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.StartNew
和Task.Run
更多Task.Yield
,因为他们都明确定义了延续代码的范围.
那么,在什么情况下await Task.Yield()
实际上有用呢?
考虑您希望异步任务返回值的情况.
现有的同步方法:
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)
后者更简洁,更易读,并且实际上并没有太多改变现有方法.
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
(我在这里谈论的是非零延迟期)实现的节流,但没有实际的、可能明显的延迟,这在某些情况下可能是不受欢迎的。
Task.Yield()
真正有用的一种情况是await
递归调用 synchronously-completed Task
s。因为 csharp\xe2\x80\x99s async
/ await
\xe2\x80\x9c通过在可能的情况下同步运行延续来释放 Zalgo\xe2\x80\x9d,所以完全同步递归场景中的堆栈可能会变得足够大,导致进程终止。我认为这也部分是由于由于间接而无法支持尾部调用Task
。await Task.Yield()
将延续安排为由调度程序运行而不是内联运行,从而避免堆栈增长并解决此问题。
此外,Task.Yield()
还可用于缩短方法的同步部分。如果调用者需要Task
在您的方法执行某些操作之前接收您的方法\xe2\x80\x99s,您可以使用Task.Yield()
强制返回Task
比自然发生的更早的时间。例如,在以下本地方法场景中,该async
方法能够Task
安全地获取对其自身的引用(假设您在单并发上运行此方法,SynchronizationContext
例如在 winforms 中或通过nito\xe2\x80\x99sAsyncContext.Run()
):
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\n3 == 3: True\n
Run Code Online (Sandbox Code Playgroud)\n\n很抱歉,我无法想象任何现实生活中的场景,其中能够强行缩短方法的同步部分async
是做某事的最佳方式。知道你可以做我刚才展示的技巧有时会很有用,但也往往更危险。通常,您可以以更好、更可读、更线程安全的方式传递数据。例如,您可以Task
使用 aTaskCompletionSource
向本地方法传递对其自身的引用:
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\n2 == 2: True\n
Run Code Online (Sandbox Code Playgroud)\n
归档时间: |
|
查看次数: |
10869 次 |
最近记录: |