.Net 线程间通信

    

    首先,你要理解 Post 跟 Send. 具体可以去参考 Windows 的消息发送函数 SendMessage 跟 PostMessage,这里不做概述。

1. SynchronizationContext 对象


 .Net 线程通信,主要使用的是 
SynchronizationContext 对象。此对象用法很简单。

首先我们看窗口程序代码:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        test();
    }

    static void test()
    {
        Output("Main Thread");

        // 获取当前线程的 SynchronizationContext 对象 
        SynchronizationContext sc = SynchronizationContext.Current;

        Action act = work;
        act.BeginInvoke(Work_CB, sc);
    }

    static void work()
    {
        Thread.Sleep(1000);
    }

    static void Work_CB(IAsyncResult ar)
    {
        Output("Worker Thread");

        // 获取主线程中的同步上下文对象
        SynchronizationContext sc = ar.AsyncState as SynchronizationContext;

        // 异步的方式和主线程通信.
        sc.Post(p => { Output(p); }, "which thread exec me ?");
    }

    static void Output(object value)
    {
        string str = string.Format("[ThreadID: {0} ] {1}", Thread.CurrentThread.ManagedThreadId, value);
        MessageBox.Show(str);
    }
}

执行结果:

[ThreadID:9] Main Thread 
[ThreadID:10] Worker Thread
[ThreadID:9] Which thread exec me ?

怎么样,得到我们预期的结果,Worker线程中调度Main线程执行了代码。这就是 SynchronizationContext 的最基本用法。

现在我们再看控制台下:

// 控制台代码
class Program
{
    static void Main(string[] args)
    {
        test();
        System.Console.Read();
    }

    static void test()
    {
        Output("Main Thread");

        // 获取当前线程的 SynchronizationContext 对象 
        SynchronizationContext sc = SynchronizationContext.Current;

        Action act = work;
        act.BeginInvoke(Work_CB, sc);
    }

    static void work()
    {
        Thread.Sleep(1000);
    }

    static void Work_CB(IAsyncResult ar)
    {
        Output("Worker Thread");

        // 获取主线程中的同步上下文对象
        SynchronizationContext sc = ar.AsyncState as SynchronizationContext;
        // 异步的方式和主线程通信.
        sc.Post(p => { Output(p); }, "which thread exec me ?");
    }

    static void Output(object value)
    {
        Console.WriteLine("[ThreadID: {0} ] {1}", Thread.CurrentThread.ManagedThreadId, value);
    }
}

编译执行。你发发现,同样的代码,在控制台下运行,程序崩溃。

调试,发现 sc 为 NULL,即 SynchronizationContext.Current 为 NULL。 既然这样,那我们就想办法,自己 New 一个 SynchronizationContext对象。

SynchronizationContext sc = new SynchronizationContext();

编译执行,结果:

我们发现,Woker线程无法调度Main线程去执行Output()函数。
这是为界面程序可以,而控制台程序不可以?

天下没有白吃的午饭,线程间能够通信,肯定也要付出代价,这个代价就是windows消息循环。当你在控制台程序里,创建了任意一个控件对象时,一个 SynchronizationContext 派生对象就会随之被之创建,被附着在创建控件对象的线程中。这样,在这个线程中调用SynchronizationContext.Current,我们就可以得到线程间通信的钥匙。当此线程的消息循环(除了消息循环,还有其他设计)被创建,我们就能通过钥匙来进行通信了。

这时候如果你了解了SendMessage和PostMessage,你就能有个详细的了解。

如果SynchronizationContext不做派生,那默认的Send和Post实现是:

public virtual void Send(SendOrPostCallback d, Object state)
{
    d(state);
}

public virtual void Post(SendOrPostCallback d, Object state)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
}

2.  ISynchronizeInvoke 接口

未完待续。

3. AsyncOperation / AsyncOperationManager 类


有没有感觉 SynchronizationContext 对象用的不舒服?由于Woker 线程没有与之关联的 SynchronizationContext 对象,我们的 test() 函数,还需要重新设计,不然还无法在控制台下运行。

重新设计的代码:

    static void test()
    {
        Output("Main Thread");

        // 不在需要了
        // SynchronizationContext sc = SynchronizationContext.Current;

        Action act = work;

        // 包装一下
        act.BeginInvoke(SyncContextCallback(Work_CB), null);
    }

    static void work()
    {
        Thread.Sleep(1000);
    }

    static void Work_CB(IAsyncResult ar)
    {
        Output("Worker Thread");

        // 也不在需要了
        // SynchronizationContext sc = ar.AsyncState as SynchronizationContext;
        // sc.Post(p => { Output(p); }, "which thread exec me ?");

        Output("which thread exec me ?");
    }

    static void Output(object value)
    {
        Console.WriteLine("[ThreadID: {0} ] {1}", Thread.CurrentThread.ManagedThreadId, value);
    }

    // 此函数将一个普通的AsyncCallback方法转换成特殊的AsyncCallback 方法
    // 它通过SynchronizationContext 来调用。这样无论线程模型中是否含有GUI线程,都可以正确的调用。
    private static AsyncCallback SyncContextCallback(AsyncCallback callback)
    {
        SynchronizationContext sc = SynchronizationContext.Current;
        if (sc == null) return callback;
        return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);
    }

可以把这段代码用UI线程和非UI线程都调用一遍,发现都能正常运行。UI线程调用,回调函数也运行在UI线程;非UI线程调用,.NET从线程池中取出一个线程执行回调函数。

SyncContextCallback() 函数其实就是AsyncOperation的基本原理。

附上 AsyncOperation/AsyncOperationManager 基本的使用代码:

static void test()
{
    Output("Main Thread");

    AsyncOperation operation = AsyncOperationManager.CreateOperation(null);
    Action act = work;
    act.BeginInvoke(Work_CB, operation);
}

static void work()
{
    Thread.Sleep(1000);
}

static void Work_CB(IAsyncResult ar)
{
    Output("Worker Thread");

    AsyncOperation operation = ar.AsyncState as AsyncOperation;
    operation.Post(p => { Output(p); }, "which thread exec me ?");
}

static void Output(object value)
{
    Console.WriteLine("[ThreadID: {0} ] {1}", Thread.CurrentThread.ManagedThreadId, value);
}


你可能感兴趣的:(通信,同步,C#,线程间)