"等待Task.Yield()"及其替代品

nos*_*tio 21 .net c# task-parallel-library async-await

如果我需要推迟代码执行,直到UI线程消息循环的未来迭代之后,我可以这样做:

await Task.Factory.StartNew(
    () => {
        MessageBox.Show("Hello!");
    },
    CancellationToken.None,
    TaskCreationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext());
Run Code Online (Sandbox Code Playgroud)

这将类似于await Task.Yield(); MessageBox.Show("Hello!");,除了我有一个选项可以取消任务,如果我想.

在使用默认同步上下文的情况下,我可以类似地使用await Task.Run继续池线程.

事实上,我喜欢Task.Factory.StartNewTask.Run更多Task.Yield,因为他们都明确定义了延续代码的范围.

那么,在什么情况下await Task.Yield()实际上有用呢?

Moh*_*oho 6

考虑您希望异步任务返回值的情况.

现有的同步方法:

public int DoSomething()
{
    return SomeMethodThatReturnsAnInt();
}
Run Code Online (Sandbox Code Playgroud)

要进行异步,请添加async关键字并更改返回类型:

public async Task<int> DoSomething()
Run Code Online (Sandbox Code Playgroud)

要使用Task.Factory.StartNew(),请将方法的单行主体更改为:

// start new task
var task = Task<int>.Factory.StartNew(
    () => {
        return SomeMethodThatReturnsAnInt();
    },
    CancellationToken.None,
    TaskCreationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext() );

// await task, return control to calling method
await task;

// return task result
return task.Result;
Run Code Online (Sandbox Code Playgroud)

与使用时添加单行相对 await Task.Yield()

// this returns control to the calling method
await Task.Yield();

// otherwise synchronous method scheduled for async execution by the 
// TaskScheduler of the calling thread
return SomeMethodThatReturnsAnInt();
Run Code Online (Sandbox Code Playgroud)

后者更简洁,更易读,并且实际上并没有太多改变现有方法.

  • 公平地说,它仍然是一行:`return await Task.Factory.StartNew(() =&gt; SomeMethodThatReturnsAnInt(), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());`。尽管如此,我明白了这一点,+1。与此有关的问题是控制流的一些非直观更改,在 [here](http://stackoverflow.com/q/18779393/1768303) 中进行了讨论。 (2认同)
  • 重新审视这一点,如果我需要将 `SomeMethodThatReturnsAnInt` 转换为 `async`,我可以简单地这样做: `public Task&lt;int&gt; DoSomething() { return Task.FromResult(SomeMethodThatReturnsAnInt()); }`。或者,对于异常传播的 [`async` 语义](http://stackoverflow.com/a/22395161/1768303):`public async Task&lt;int&gt; DoSomething() { return await Task.FromResult(SomeMethodThatReturnsAnInt()); }`。显然,`await Task.Yield()` 在这里是多余的和不受欢迎的。很抱歉撤销我的投票。 (2认同)

Kir*_*kiy 5

Task.Yield()非常适合在方法的其他同步部分“打孔” async

就我个人而言,我发现它在我有一个可以在极短的时间内多次调用的自取消async方法(一种管理自己的相应方法CancellationTokenSource并在每次后续调用中取消先前创建的实例的方法)时很有用(即通过相互依赖的 UI 元素的事件处理程序)。在这种情况下,一旦被换出,就使用Task.Yield()后跟IsCancellationRequested检查CancellationTokenSource可以防止进行潜在的昂贵工作,而这些工作的结果无论如何最终都会被丢弃。

这是一个示例,其中只有对 SelfCancellingAsync 的最后一个排队调用才能执行昂贵的工作并运行完成。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskYieldExample
{
    class Program
    {
        private static CancellationTokenSource CancellationTokenSource;

        static void Main(string[] args)
        {
            SelfCancellingAsync();
            SelfCancellingAsync();
            SelfCancellingAsync();

            Console.ReadLine();
        }

        private static async void SelfCancellingAsync()
        {
            Console.WriteLine("SelfCancellingAsync starting.");

            var cts = new CancellationTokenSource();
            var oldCts = Interlocked.Exchange(ref CancellationTokenSource, cts);

            if (oldCts != null)
            {
                oldCts.Cancel();
            }

            // Allow quick cancellation.
            await Task.Yield();

            if (cts.IsCancellationRequested)
            {
                return;
            }

            // Do the "meaty" work.
            Console.WriteLine("Performing intensive work.");

            var answer = await Task
                .Delay(TimeSpan.FromSeconds(1))
                .ContinueWith(_ => 42, TaskContinuationOptions.ExecuteSynchronously);

            if (cts.IsCancellationRequested)
            {
                return;
            }

            // Do something with the result.
            Console.WriteLine("SelfCancellingAsync completed. Answer: {0}.", answer);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这里的目标是允许在SynchronizationContext对异步方法的非等待调用返回后立即同步执行的代码(当它命中第一个时await)改变影响异步方法执行的状态。这很像通过Task.Delay(我在这里谈论的是非零延迟期)实现的节流,但没有实际的、可能明显的延迟,这在某些情况下可能是不受欢迎的。


bin*_*nki 5

Task.Yield()真正有用的一种情况是await递归调用 synchronously-completed Tasks。因为 csharp\xe2\x80\x99s async/ await \xe2\x80\x9c通过在可能的情况下同步运行延续来释放 Zalgo\xe2\x80\x9d,所以完全同步递归场景中的堆栈可能会变得足够大,导致进程终止。我认为这也部分是由于由于间接而无法支持尾部调用Taskawait Task.Yield()将延续安排为由调度程序运行而不是内联运行,从而避免堆栈增长并解决此问题。

\n\n

此外,Task.Yield()还可用于缩短方法的同步部分。如果调用者需要Task在您的方法执行某些操作之前接收您的方法\xe2\x80\x99s,您可以使用Task.Yield()强制返回Task比自然发生的更早的时间。例如,在以下本地方法场景中,该async方法能够Task安全地获取对其自身的引用(假设您在单并发上运行此方法,SynchronizationContext例如在 winforms 中或通过nito\xe2\x80\x99sAsyncContext.Run()):

\n\n
using Nito.AsyncEx;\nusing System;\nusing System.Threading.Tasks;\n\nclass Program\n{\n    // Use a single-threaded SynchronizationContext similar to winforms/WPF\n    static void Main(string[] args) => AsyncContext.Run(() => RunAsync());\n\n    static async Task RunAsync()\n    {\n        Task<Task> task = null;\n        task = getOwnTaskAsync();\n        var foundTask = await task;\n        Console.WriteLine($"{task?.Id} == {foundTask?.Id}: {task == foundTask}");\n\n        async Task<Task> getOwnTaskAsync()\n        {\n            // Cause this method to return and let the \xe3\x80\x8ctask\xe3\x80\x8d local be assigned.\n            await Task.Yield();\n            return task;\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出:

\n\n
3 == 3: True\n
Run Code Online (Sandbox Code Playgroud)\n\n

很抱歉,我无法想象任何现实生活中的场景,其中能够强行缩短方法的同步部分async是做某事的最佳方式。知道你可以做我刚才展示的技巧有时会很有用,但也往往更危险。通常,您可以以更好、更可读、更线程安全的方式传递数据。例如,您可以Task使用 aTaskCompletionSource向本地方法传递对其自身的引用:

\n\n
using System;\nusing System.Threading.Tasks;\n\nclass Program\n{\n    // Fully free-threaded! Works in more environments!\n    static void Main(string[] args) => RunAsync().Wait();\n\n    static async Task RunAsync()\n    {\n        var ownTaskSource = new TaskCompletionSource<Task>();\n        var task = getOwnTaskAsync(ownTaskSource.Task);\n        ownTaskSource.SetResult(task);\n        var foundTask = await task;\n        Console.WriteLine($"{task?.Id} == {foundTask?.Id}: {task == foundTask}");\n\n        async Task<Task> getOwnTaskAsync(\n            Task<Task> ownTaskTask)\n        {\n            // This might be clearer.\n            return await ownTaskTask;\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出:

\n\n
2 == 2: True\n
Run Code Online (Sandbox Code Playgroud)\n


归档时间:

查看次数:

10869 次

最近记录:

7 年,9 月 前