async/await的多线程问题

今天尝试把.net4.5新增的异步编程模型async/await加入本身的框架,由于从第一印象看,使用async/await的写法实在太方便了,以同步代码的方式写异步流程,写起来更顺畅,不容易打断思路,异常传递、资源控制(lock和using)也都完美支持,即便有少许的性能损失,也彻底能够接受。编程

首先我写了一个测试代码,以熟悉async/await模型,代码以下:框架

static class TestCase
{
    static async Task Test2()
    {
        Console.WriteLine("3 {0}", Thread.CurrentThread.ManagedThreadId);
        await Task.Run(() =>
        {
            Thread.Sleep(1000);
        });// 这里能够换成Task.Delay(1000);
        Console.WriteLine("4 {0}", Thread.CurrentThread.ManagedThreadId);
    }

    static async Task Test1()
    {
        Console.WriteLine("1 {0}", Thread.CurrentThread.ManagedThreadId);
        await Test2();
        Console.WriteLine("2 {0}", Thread.CurrentThread.ManagedThreadId);
    }

    static void Main(string[] args)
    {
        Test1().Wait();
    }
}

输出以下:异步

output

代码很简单,结果也看似正确,直到我发现一个大问题:async

await后面的代码是在另外一个线程执行的!!!异步编程

这不只会影响到框架的运行,还会致使线程争用资源的问题,也就是说看似同一处的代码,会运行在不一样线程,而且有可能并行。若是访问公共资源(如静态变量)还须要加锁,严重影响编程体验和性能。我推测,极可能Task类自带的建立Task的静态函数所产生的Task,设置Complete状态时都是在其余线程,所以致使了await的回调也在该线程执行。因而我把代码稍微改了一下:函数

static class TestCase
{
    static TaskCompletionSource<object> source = new TaskCompletionSource<object>();

    static async Task Test2()
    {
        Console.WriteLine("3 {0}", Thread.CurrentThread.ManagedThreadId);
        await source.Task;
        Console.WriteLine("4 {0}", Thread.CurrentThread.ManagedThreadId);
    }

    static async Task Test1()
    {
        Console.WriteLine("1 {0}", Thread.CurrentThread.ManagedThreadId);
        await Test2();
        Console.WriteLine("2 {0}", Thread.CurrentThread.ManagedThreadId);
    }

    static void Main(string[] args)
    {
        Task task = Test1();
        Thread.Sleep(1000);
        source.SetResult(null);
        task.Wait(); // 这一句实际上是没有必要的,而且若是放在SetResult前,会致使死锁
    }
}

输出以下:oop

image

果真所有是在主线程执行了。其中source.SetResult(null);这一句放到框架中,当条件合适时就在主线程Loop中调用,以便唤醒await继续执行剩下的过程。此外,Task类的静态函数所产生的Task,也能够经过一个包装函数,来让在其余线程执行的SetResult,Queue到主线程调用,相似这样:性能

public Task<T> Wrap<T>(Task<T> task)
{
    Loop loop = Current;
    TaskCompletionSource<T> source = new TaskCompletionSource<T>();
    task.GetAwaiter().OnCompleted(() =>
    {
        loop.Execute(() =>
        {
            if (task.IsCompleted)
                source.TrySetResult(task.Result);
            else if (task.IsCanceled)
                source.TrySetCanceled();
            else
                source.TrySetException(task.Exception);
        });
    });
    return source.Task;
}
相关文章
相关标签/搜索