Note that there are some explanatory texts on larger screens.

plurals
  1. POHow to force C# asynchronous operations to run in a...synchronized manner?
    primarykey
    data
    text
    <p>I have these two classes: LineWriter and AsyncLineWriter. My goal is to allow a caller to queue up multiple pending asynchronous invocations on AsyncLineWriter’s methods, but under the covers never truly allow them to run in parallel; i.e., they must somehow queue up and wait for one another. That's it. The rest of this question gives a complete example so there's absolutely no ambiguity about what I'm really asking.</p> <p>LineWriter has a single synchronous method (WriteLine) that takes about 5 seconds to run :</p> <pre><code>public class LineWriter { public void WriteLine(string line) { Console.WriteLine("1:" + line); Thread.Sleep(1000); Console.WriteLine("2:" + line); Thread.Sleep(1000); Console.WriteLine("3:" + line); Thread.Sleep(1000); Console.WriteLine("4:" + line); Thread.Sleep(1000); Console.WriteLine("5:" + line); Thread.Sleep(1000); } } </code></pre> <p>AsyncLineWriter just encapsulates LineWriter and provides an asynchronous interface (BeginWriteLine and EndWriteLine):</p> <pre><code>public class AsyncLineWriter { public AsyncLineWriter() { // Async Stuff m_LineWriter = new LineWriter(); DoWriteLine = new WriteLineDelegate(m_LineWriter.WriteLine); // Locking Stuff m_Lock = new object(); } #region Async Stuff private LineWriter m_LineWriter; private delegate void WriteLineDelegate(string line); private WriteLineDelegate DoWriteLine; public IAsyncResult BeginWriteLine(string line, AsyncCallback callback, object state) { EnterLock(); return DoWriteLine.BeginInvoke(line, callback, state); } public void EndWriteLine(IAsyncResult result) { DoWriteLine.EndInvoke(result); ExitLock(); } #endregion #region Locking Stuff private object m_Lock; private void EnterLock() { Monitor.Enter(m_Lock); Console.WriteLine("----EnterLock----"); } private void ExitLock() { Console.WriteLine("----ExitLock----"); Monitor.Exit(m_Lock); } #endregion } </code></pre> <p>As I said in the first paragraph, my goal is to only allow one pending asynchronous operation at a time. I’d <em>really</em> like it if I didn’t need to use locks here; i.e., if BeginWriteLine could return an IAsyncResult handle which always returns immediately, and yet retains the expected behavior, that would be great, but I can’t figure out how to do that. So the next best way to explain what I'm after seemed to be to just use locks. </p> <p>Still, my “Locking Stuff” section isn’t working as expected. I would expect the above code (which runs EnterLock before an async operation begins and ExitLock after an async operation ends) to only allow one pending async operation to run at any given time. </p> <p>If I run the following code:</p> <pre><code>static void Main(string[] args) { AsyncLineWriter writer = new AsyncLineWriter(); var aresult = writer.BeginWriteLine("atest", null, null); var bresult = writer.BeginWriteLine("btest", null, null); var cresult = writer.BeginWriteLine("ctest", null, null); writer.EndWriteLine(aresult); writer.EndWriteLine(bresult); writer.EndWriteLine(cresult); Console.WriteLine("----Done----"); Console.ReadLine(); } </code></pre> <p>I <em>expect</em> to see the following output:</p> <pre><code>----EnterLock---- 1:atest 2:atest 3:atest 4:atest 5:atest ----ExitLock---- ----EnterLock---- 1:btest 2:btest 3:btest 4:btest 5:btest ----ExitLock---- ----EnterLock---- 1:ctest 2:ctest 3:ctest 4:ctest 5:ctest ----ExitLock---- ----Done---- </code></pre> <p>But instead I see the following, which means they’re all just running in parallel as if the locks had no effect:</p> <pre><code>----EnterLock---- ----EnterLock---- ----EnterLock---- 1:atest 1:btest 1:ctest 2:atest 2:btest 2:ctest 3:atest 3:btest 3:ctest 4:atest 4:btest 4:ctest 5:atest 5:btest 5:ctest ----ExitLock---- ----ExitLock---- ----ExitLock---- ----Done---- </code></pre> <p>I’m assuming the locks are “ignored” because (and correct me if I’m wrong) I’m locking them all from the same thread. My question is: how <em>can</em> I get my expected behavior? And “don’t use asynchronous operations” is not an acceptable answer. </p> <p>There is a bigger, more complex, real life use case for this, using asynchronous operations, where sometimes it’s not possible to have multiple truly parallel operations, but I need to at least emulate the behavior even if it means queuing the operations and running them one after another. Specifically, the interface to AsyncLineWriter shouldn’t change, but it’s internal behavior somehow needs to queue up any async operations it’s given in a thread-safe manner. Another gotcha is that I can't add locks into LineWriter's WriteLine, because in my real case, this is a method which I can't change (although, in this example, doing so actually does give me the expected output).</p> <p>A link to some code designed to solve a similar issue might be good enough to get me on the right path. Or perhaps some alternative ideas. Thanks.</p> <p><em>P.S. If you're wondering what kind of use case could possibly use such a thing: it's a class which maintains a network connection, upon which only one operation can be active at a time; I'm using asynchronous calls for each operation. There's no obvious way to have truly parallel lines of communication go on over a single network connection, so they will need to wait for each other in one way or another.</em></p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload