当前位置: 首页 > 工具软件 > Spring Sync > 使用案例 >

同步 SynchronizationContext

东郭自强
2023-12-01

 

 

Introduction

The SynchronizationContext class is a new class belonging to the .NET Framework's System.Threading namespace. The purpose of this class is to provide a model to make communication between threads easier and more robust. I will begin by describing how the SynchronizationContext class helps us handle events using Windows Forms. I will also touch on a few of the other new classes in the System.ComponentModel which use the SynchronizationContext class that also help us with synchronization issues.

As a disclaimer, some of what I describe below is based on my experience, albeit brief, with these new classes rather than documentation. The documentation seems a bit sparse at this time. So while my understanding isn't complete, I'm hoping that what I share below will be helpful, and that I will be able to expand this article in the future as my understanding (and hopefully Microsoft's documentation) increases.

Background

在windows应用窗体应用程序中,对窗体上控件属性的任何修改都必须在主线程中完成。不能从其他线程
安全地访问控件的方法和属性。

Most of us are familiar with the prohibition against modifying a Control from any thread other than the one in which it was created. Instead, an event generated on another thread must be marshaled to the Control's thread using its (or the Form

that it belongs to) Invoke(调用) or BeginInvoke methods. These two methods belong to the ISynchronizeInvoke interface, which the Control class implements. Their purpose

is to take a delegate and invoke it on the same thread that the ISynchronizeInvoke object is running. Typically, a Form's method for responding to an event generated

on another thread looks like this:

 private void HandleSomeEvent(object sender, EventArgs e)
{
    if(InvokeRequired)
    {
        BeginInvoke(new EventHandler(HandleSomeEvent), sender, e);
    }
    else
    {
        //
Event logic here.
    }
}

This method first checks its InvokeRequired boolean property, which also belongs to the ISynchronizeInvoke interface, to see if it needs to marshal the event to its thread. If the property is true, it calls the BeginInvoke method, passing it a delegate to the method for handling the event and its arguments. If the property is false, it executes its logic for responding to the event. In other words, the InvokeRequired property will be true if it is checked on a thread other than the one in which the Form belongs; otherwise, it will be false.

Note that the delegate being passed to BeginInvoke represents the same method that handled the event in the first place. If the InvokeRequired property is true, this has the effect of invoking the event handler method twice: once when it initially is called in response to the event, and once after it has been marshaled by the Form with a call to BeginInvoke. The second time around, the InvokeRequired property will be false and the event logic will be run.

 

Using the SynchronizationContext Class

How do we write our own classes that use the SynchronizationContext class? The key thing to remember is that our goal is to get the SynchronizationContext belonging to the thread in which our class was created so that we can use it later from another thread to send/post events to the original thread. I'm going to give a really simple example. This example does nothing useful, but almost at a glance, it will show us the basic template for using the SynchronizationContext class:

using System;
using System.Threading;

namespace SynchronizationContextExample
{
    public class MySynchronizedClass
    {
        private Thread workerThread;

        private SynchronizationContext context;

        public event EventHandler SomethingHappened;

        public MySynchronizedClass()
        {
            // It's important to get the current SynchronizationContext
            // object here in the constructor. We want the 
            // SynchronizationContext object belonging to the thread in
            // which this object is being created.
            context = SynchronizationContext.Current;

            // It's possible that the current thread does not have a 
            // SynchronizationContext object; a SynchronizationContext
            // object has not been set for this thread.
            //
            // If so, we simplify things by creating a SynchronizationContext
            // object ourselves. However! There could be some problems with 
            // this approach. See the article for more details.
            if(context == null)
            {
                context = new SynchronizationContext();
            }

            workerThread = new Thread(new ThreadStart(DoWork));

            workerThread.Start();
        }

        private void DoWork()
        {
            context.Post(new SendOrPostCallback(delegate(object state)
            {
                EventHandler handler = SomethingHappened;

                if(handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
        }
    }
}

This class gets the SynchronizationContext for the current thread in its constructor. If this class is being used in a Form, the current SynchronizationContext object will allow us to post/send events to the Form's thread. It's possible, however, that if an instance of this class is being created in, say, a worker thread somewhere, the SynchronizationContext.Current property may be null. In other words, the SynchronizationContext object for the current thread may not have been set. So, it's important to check to see if the Current property is null. Here, in the case where it is null, I just create an instance of the SynchronizationContext class and rely on its default behavior.

What's nice is that our class doesn't have to know about who it's sending/posting events to. It doesn't have to have an ISynchronizeInvoke object passed to it to synchronize itself with; it has access to the current SynchronizationContext object through the SynchronizationContext.Current property.

Warning! As was pointed out in a post to this article's message board, creating an instance of the SynchronizationContext class can be dangerous. For example, say that you're writing a Form based application and an object needs to interact with the main Form in a thread safe way; the object needs access to the SynchronizationContext that belongs to the Form's thread. The object can retrieve this by accessing the SynchronizationContext.Current property. This will give you the Form's SynchronizationContext derived object assuming that the Current property is checked on the same thread in which the Form is running. If it's checked from another thread, the Current property will probably be null. If this is the case, it would be safer to treat this as an error rather than simply create an instance of the SynchronizationContext class.

 

 类似资料: