none
Asynchronous Operation in MultiThreaded Application RRS feed

  • Question

  • I have an application in which UI Thread spawns a new thread (Thread A) that executes method in ClassA (StartRead).

    Method StartRead

    1. creates instance of reader (member of ClassA) if is not instantiated, 

    2. initiate asynchronous read (BeginRead)

    3. infinite loop untill read is completed to make sure the thread is alive to receive callback.

    Begin Read takes 2 parameters

    1. AsyncCallback - callback function that will be executed when read is completed.

    2. Object - AsyncState that is used to create AsyncOperation. This AsyncOperation is then used to determine the thread on which the AsyncCallback will be executed.

    In my application when BeginRead is called, AsyncCallback is set to Read_Callback (method of ClassA), Object is set to reader (member of ClassA (instantiated in StartRead method executed on Thread A)).

    What I am expecting in my application, is that Read_Callback should be executed on Thread A, as I am using AsyncOperation created using reader (member of ClassA) to determine SynchronizationContext using which Callback is executed. However that is not what is happening. Read_Callback is executed not on Thread A or UI Thread. It executes on some other thread always.

    What is going on behind the scene - any clue?

    Someone might argue that if thread is created to initiate async operation and after that if that thread is waiting for the operation to complete, why Async operation was initiated, but point here is to understand the working of thread in the given scenario.


    shura



    Monday, March 19, 2012 6:09 PM

Answers

  • In short, when we want to post/send an operation to UI thread, we can use SynchronizationContext of UI thread, however, if we need to
    post/send operations to non-UI thread, SynchronizationContext doesn’t work.

    The reason is UI thread employs WindowsFormsSynchronizationContext as SynchronizationContext, this SynchronizationContext uses the ISynchronizeInvoke methods on a UI control, which passes the delegates to the underlying Win32 message loop. The context for WindowsFormsSynchronizationContext is a single UI thread. All delegates queued to the WindowsFormsSynchronizationContext are executed one at a time; they’re executed by a specific UI thread in the order they were queued. The current implementation creates one WindowsFormsSynchronizationContext for each UI thread.

    Non-UI thread uses SynchronizationContext: The default SynchronizationContext is a default-constructed SynchronizationContext object.
    By convention, if a thread’s current SynchronizationContext is null, then it implicitly has a default SynchronizationContext. The default SynchronizationContext queues its asynchronous delegates to the ThreadPool but executes its synchronous delegates directly on the calling thread. Therefore, its context covers all ThreadPool threads as well as any thread that calls Send. The context “borrows” threads that call Send, bringing them into its context until the delegate completes. In this sense, the default context may include any thread in the process.

    Reference:

    [1] It's All About the SynchronizationContext

    http://msdn.microsoft.com/en-us/magazine/gg598924.aspx

    [2] Understanding SynchronizationContext

    http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I



    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.

    Regards,
    Eric Yang
    Microsoft Online Community Support

    • Marked as answer by Bhavesh Shura Tuesday, April 3, 2012 12:06 PM
    Tuesday, April 3, 2012 7:30 AM

All replies

  • In order for this to work, your BeginRead method would have to capture the SynchronizationContext.Current, and store it.  When you're done, you could use SynchronizationContext.Post to post the message back onto the UI thread.

    However, this is difficult to get right (always).  You should also check to make sure that SynchronizationContext.Current isn't null (this will happen if it's called on a threadpool thread, for example), in which case, you'd want to raise it normally.

    Be aware that this becomes MUCH simpler when using the TPL in .NET 4, and even more so in C# 5 with the new async/await support.  I would, personally, just plan for this and design your API to work with Tasks instead.  This will make it work seamlessly with C# 5 when it's released, and also make it simpler now.  For example, if your operation (BeginRead) is going to return a series of bytes, you could do this instead:

    // Make the operation return Task<YourReturnType>
    public Task<byte[]> ReadAsync()
    {
        return Task.Factory.StartNew( () =>
        {
            // This will run in the threadpool, so you can use
            // sync operations as needed
            byte[] data = this.ReadSynchronously();
            return data;
        });
    }
    
    
    // When you want to use it, the caller now has more options, including doing:
    
    var readOp = yourClass.ReadAsync();
    
    readOp.ContinueWith(t =>
    {
       // This will now run on the UI thread when the operation is completed!
       byte[] data = t.Result; // Get the data
       this.ProcessData(data); 
    }, TaskScheduler.FromCurrentSynchronizationContext());

    The nice thing with the above, is that it makes using it later (in C# 5) even easier:

    byte[] data = await yourClass.ReadAsync();
    // Use data...

     

    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Monday, March 19, 2012 6:41 PM
    Moderator
  • Thanks Reed for quick response.

    I am using reader (member of ClassA) to create AsyncOperation using AsyncOperationManager.CreateOperation. And I am storing this AsyncOperation to retrieve the required SynchronizationContext from property SynchronizationContext. And then I use this context, stored in AsyncOperation, to Post the callback method. Given this I would like to know the cases, when SynchronizationContext can be null. Specially when thread is not from ThreadPool.

    Also can you also put light on why Read_Callback is not executing on Thread A, in this case?

    Thanks


    shura

    Monday, March 19, 2012 6:52 PM
  • Are you setting SynchronizationContext to SynchronizationContext.Current?  If so, the context shouldn't be null if you're calling it from a UI thread.  If you call this from a ThreadPool thread, the context will be null, though, as it's only setup by certain frameworks.

    That being said, as I mentioned, I'd really argue that writing in terms of Task/Task<T> is really a better model for any new development with asynchronous method calls...


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Monday, March 19, 2012 8:59 PM
    Moderator
  • First of all this is not new development, I am debugging the existing code developed in C++/CLI to fix some issues with threading.

    And, I am not setting SynchronizationContext to anything. But do I need to set that explicitly when using AsyncOperation created with call System::ComponentModel::AsyncOperationManager::CreateOperation(reader); where reader is object created on user created thread - Thread A and not a thread from ThreadPool.


    shura

    Monday, March 19, 2012 9:07 PM
  • In short, when we want to post/send an operation to UI thread, we can use SynchronizationContext of UI thread, however, if we need to
    post/send operations to non-UI thread, SynchronizationContext doesn’t work.

    The reason is UI thread employs WindowsFormsSynchronizationContext as SynchronizationContext, this SynchronizationContext uses the ISynchronizeInvoke methods on a UI control, which passes the delegates to the underlying Win32 message loop. The context for WindowsFormsSynchronizationContext is a single UI thread. All delegates queued to the WindowsFormsSynchronizationContext are executed one at a time; they’re executed by a specific UI thread in the order they were queued. The current implementation creates one WindowsFormsSynchronizationContext for each UI thread.

    Non-UI thread uses SynchronizationContext: The default SynchronizationContext is a default-constructed SynchronizationContext object.
    By convention, if a thread’s current SynchronizationContext is null, then it implicitly has a default SynchronizationContext. The default SynchronizationContext queues its asynchronous delegates to the ThreadPool but executes its synchronous delegates directly on the calling thread. Therefore, its context covers all ThreadPool threads as well as any thread that calls Send. The context “borrows” threads that call Send, bringing them into its context until the delegate completes. In this sense, the default context may include any thread in the process.

    Reference:

    [1] It's All About the SynchronizationContext

    http://msdn.microsoft.com/en-us/magazine/gg598924.aspx

    [2] Understanding SynchronizationContext

    http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I



    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.

    Regards,
    Eric Yang
    Microsoft Online Community Support

    • Marked as answer by Bhavesh Shura Tuesday, April 3, 2012 12:06 PM
    Tuesday, April 3, 2012 7:30 AM