[英]"await Task.Yield()" and its alternatives
如果我需要將代碼執行推遲到 UI 線程消息循環的未來迭代之后,我可以這樣做:
await Task.Factory.StartNew(
() => {
MessageBox.Show("Hello!");
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
這類似於await Task.Yield(); MessageBox.Show("Hello!");
await Task.Yield(); MessageBox.Show("Hello!");
,此外,如果我願意,我可以選擇取消任務。
在使用默認同步上下文的情況下,我可以類似地使用await Task.Run
在池線程上繼續。
事實上,我更喜歡Task.Factory.StartNew
和Task.Run
而不是Task.Yield
,因為它們都明確定義了延續代碼的范圍。
那么,在什么情況下await Task.Yield()
真的有用呢?
Task.Yield()
非常適合在async
方法的其他同步部分“打孔”。
就我個人而言,我發現它在我有一個可以在極短的時間內多次調用的自取消async
方法(管理自己相應的CancellationTokenSource
並在每次后續調用中取消先前創建的實例的方法)的情況下很有用(即通過相互依賴的 UI 元素的事件處理程序)。 在這種情況下,一旦CancellationTokenSource
被換出,使用Task.Yield()
后跟IsCancellationRequested
檢查可以防止做潛在的昂貴工作,其結果最終將被丟棄。
這是一個示例,其中只有對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);
}
}
}
這里的目標是允許在對 async 方法的非等待調用返回后立即在同一個SynchronizationContext
上同步執行的代碼(當它遇到第一個await
時)來更改影響 async 方法執行的狀態。 這與Task.Delay
實現的節流非常相似(我在這里談論的是非零延遲期),但沒有實際的、潛在的明顯延遲,這在某些情況下可能不受歡迎。
考慮您希望異步任務返回值的情況。
現有同步方法:
public int DoSomething()
{
return SomeMethodThatReturnsAnInt();
}
要進行異步,請添加 async 關鍵字並更改返回類型:
public async Task<int> DoSomething()
要使用 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;
與如果您使用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();
后者更加簡潔、易讀,並且實際上並沒有太多改變現有的方法。
Task.Yield()
真正有用的一種情況是當您await
遞歸調用同步完成的Task
時。 因為 csharp 的async
/ await
通過盡可能同步運行延續來“釋放 Zalgo” ,所以完全同步遞歸場景中的堆棧可以變得足夠大,以至於您的進程死亡。 我認為這也部分是由於Task
間接導致無法支持尾調用。 await Task.Yield()
安排由調度程序而不是內聯運行延續,從而避免堆棧增長並解決此問題。
此外, Task.Yield()
可用於縮短方法的同步部分。 如果調用者需要在您的方法執行某些操作之前接收您的方法的Task
,您可以使用Task.Yield()
強制提前返回Task
,否則會自然發生。 例如,在以下本地方法場景中, async
方法能夠安全地獲取對其自身Task
的引用(假設您在單並發SynchronizationContext
上運行它,例如在 winforms 中或通過nito 的AsyncContext.Run()
):
using Nito.AsyncEx;
using System;
using System.Threading.Tasks;
class Program
{
// Use a single-threaded SynchronizationContext similar to winforms/WPF
static void Main(string[] args) => AsyncContext.Run(() => RunAsync());
static async Task RunAsync()
{
Task<Task> task = null;
task = getOwnTaskAsync();
var foundTask = await task;
Console.WriteLine($"{task?.Id} == {foundTask?.Id}: {task == foundTask}");
async Task<Task> getOwnTaskAsync()
{
// Cause this method to return and let the 「task」 local be assigned.
await Task.Yield();
return task;
}
}
}
輸出:
3 == 3: True
很抱歉,我想不出任何現實生活中的場景,其中能夠強制縮短async
方法的同步部分是做某事的最佳方式。 知道你可以像我剛剛展示的那樣做一個技巧有時會很有用,但它也往往更危險。 通常,您可以以更好、更易讀和更線程安全的方式傳遞數據。 例如,您可以使用TaskCompletionSource
向本地方法傳遞對其自身Task
的引用:
using System;
using System.Threading.Tasks;
class Program
{
// Fully free-threaded! Works in more environments!
static void Main(string[] args) => RunAsync().Wait();
static async Task RunAsync()
{
var ownTaskSource = new TaskCompletionSource<Task>();
var task = getOwnTaskAsync(ownTaskSource.Task);
ownTaskSource.SetResult(task);
var foundTask = await task;
Console.WriteLine($"{task?.Id} == {foundTask?.Id}: {task == foundTask}");
async Task<Task> getOwnTaskAsync(
Task<Task> ownTaskTask)
{
// This might be clearer.
return await ownTaskTask;
}
}
}
輸出:
2 == 2: True
Task.Yield
不是Task.Factory.StartNew
或Task.Run
的替代品。 他們完全不同。 當您await
Task.Yield
時,您允許當前線程上的其他代碼在不阻塞線程的情況下執行。 把它想象成等待Task.Delay
,除了Task.Yield
等到任務完成,而不是等待一個特定的時間。
注意:不要在 UI 線程上使用Task.Yield
並假設 UI 將始終保持響應。 並非總是如此。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.