1

我正在尝试在已经存在的非常大的同步代码库中使用 async/await。这个代码库中有一些全局状态可以在同步上下文中正常工作,如果 kludgy 的话,但它在 async/await 的异步上下文中不起作用。

所以,我的两个选择似乎是要么排除全局上下文,这将是一项非常大且非常耗时的任务,要么在继续运行时做一些聪明的事情。

为了更好地理解 async/await 和 continuation,我制作了一个测试程序,如下所示。显示在这里。

    // A method to simulate an Async read of the database.
    private static Task ReadAsync()
    {
        return Task.Factory.StartNew(() =>
        {
            int max = int.MaxValue / 2;
            for (int i = 0; i < max; ++i)
            {
            }
        });
    }

    // An async method that creates several continuations.
    private static async Task ProcessMany(int i)
    {
        Console.WriteLine(string.Format("{0} {1}", i.ToString(), 0));

        await ReadAsync();

        Console.WriteLine(string.Format("{0} {1}", i.ToString(), 1));

        await ReadAsync();

        Console.WriteLine(string.Format("{0} {1}", i.ToString(), 2));

        await ReadAsync();

        Console.WriteLine(string.Format("{0} {1}", i.ToString(), 3));
    }


    public static void Main(string[] args)
    {
        Queue<Task> queue = new Queue<Task>();

        for (int i = 0; i < 10; ++i)
        {
            queue.Enqueue(ProcessMany(i));
        }

        // Do some synchonous processing...
        Console.WriteLine("Processing... ");

        for (int i = 0; i < int.MaxValue; ++i)
        {
        }

        Console.WriteLine("Done processing... ");

        queue.Dequeue().Wait();
    }

在阅读了所有关于 async/await 的内容后,我的理解是“处理中......”和“完成处理......”WriteLines 之间不会发生任何延续。

这是一些示例输出。

0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
Processing...
3 1
2 1
7 1
6 1
0 1
4 1
5 1
1 1
6 2
3 2
Done processing...
7 2
2 2
0 2
4 2
5 2
1 2
6 3
3 3
7 3
2 3
0 3

我希望程序结束时的单个 Wait() 在第一个完成时可能会产生多个延续,但我不明白如何在“处理...”和“完成处理”之间运行任何延续……”。我认为 Console.WriteLine 方法中可能有产量或其他东西,所以我完全替换了它,但这并没有改变输出。

我对 async/await 的理解显然存在差距。当我们只是增加一个变量时,怎么会发生延续?编译器或 CLR 是否在这里注入了某种魔法?

提前感谢您在更好地理解 async/await 和延续方面提供的任何帮助。

编辑:

如果您按照斯蒂芬的评论以这种方式编辑示例代码,那么发生的事情会更加明显。

    // An async method that creates several continuations.
    private static async Task ProcessMany(int i)
    {
        Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 0, Thread.CurrentThread.ManagedThreadId));

        await ReadAsync();

        Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 1, Thread.CurrentThread.ManagedThreadId));

        await ReadAsync();

        Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 2, Thread.CurrentThread.ManagedThreadId));

        await ReadAsync();

        Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 3, Thread.CurrentThread.ManagedThreadId));
    }


    public static void Main(string[] args)
    {
        Queue<Task> queue = new Queue<Task>();

        for (int i = 0; i < 10; ++i)
        {
            queue.Enqueue(ProcessMany(i));
        }

        // Do some synchonous processing...
        Console.WriteLine("Processing... {0}", Thread.CurrentThread.ManagedThreadId);

        for (int i = 0; i < int.MaxValue; ++i)
        {
        }

        Console.WriteLine("Done processing... {0}", Thread.CurrentThread.ManagedThreadId);

        queue.Dequeue().Wait();
    }

输出:

0 0 9
1 0 9
2 0 9
3 0 9
4 0 9
5 0 9
6 0 9
7 0 9
8 0 9
9 0 9
Processing... 9
4 1 14
3 1 13
2 1 12
5 1 15
0 1 10
6 1 16
1 1 11
7 1 17
4 2 14
3 2 13
0 2 10
6 2 16
2 2 12
5 2 15
Done processing... 9
1 2 11
7 2 17
0 3 10
4 3 14
4

2 回答 2

5

如果您没有当前的SynchronizationContextor TaskScheduler,那么延续将在线程池线程上执行(与主线程分开)。在控制台应用程序中就是这种情况,但您会在 WinForms/WPF/ASP.NET 中看到非常不同的行为。

虽然您可以通过使用 custom 来控制继续调度TaskScheduler,但这将是一项相当多的工作,而且可能几乎没有什么好处。我不清楚您的全局状态有什么问题,但请考虑替代方案,例如SemaphoreSlim.

于 2013-05-29T18:59:44.517 回答
0

只要您在 ProcessMany 中调用以下行,对 ProcessMany 的每次调用都会立即在单独线程池中的单独线程中执行。

await ...;

这就是为什么您在“处理”打印输出之前看到一堆调用的原因。因此,当您执行所有这 10 个 ProcessMany 调用时,您将开始运行您的大循环。由于那个大循环在你的主线程上运行,10 个 ProcessMany 调用继续在它们的线程中执行,产生额外的打印输出。看起来您的 ProcessMany 调用在您的主线程循环之前没有完成执行,因此它们在您的“完成处理”打印输出之后继续吐出更多结果。

我希望这可以为您澄清事情的顺序。

于 2013-05-29T19:07:01.430 回答