[英]Best asynchronous while method
我需要編寫一些異步代碼,這些代碼本質上是試圖反復與數據庫進行對話和初始化。 通常,第一次嘗試將失敗,因此需要重試。
在過去的日子里,我會使用類似於以下的模式:
void WaitForItToWork()
{
bool succeeded = false;
while (!succeeded)
{
// do work
succeeded = outcome; // if it worked, mark as succeeded, else retry
Threading.Thread.Sleep(1000); // arbitrary sleep
}
}
我意識到最近在.NET方面對異步模式進行了很多更改,因此我的問題確實是這是使用的最佳方法,還是在探索async
內容時是否值得,如果是的話,如何在async
實現此模式?
更新資料
為了澄清起見,我想異步生成該工作,以便生成它的方法不必等待它完成,因為它將在服務的構造函數中產生,因此構造函數必須立即返回。
您可以像這樣重構該片段:
async Task<bool> WaitForItToWork()
{
bool succeeded = false;
while (!succeeded)
{
// do work
succeeded = outcome; // if it worked, make as succeeded, else retry
await Task.Delay(1000); // arbitrary delay
}
return succeeded;
}
顯然,它將給您帶來的唯一好處是更有效地使用線程池,因為它並不總是需要整個線程來使延遲發生。
根據獲得outcome
,可能會有更有效的方法來使用async/await
完成此工作。 通常,您可能會遇到類似GetOutcomeAsync()
,該問題會以自然方式異步進行Web服務,數據庫或套接字的調用,因此您只需要執行var outcome = await GetOutcomeAsync()
。
重要的是要考慮到, WaitForItToWork
將由編譯器拆分為多個部分,而來自await
行的部分將異步繼續。 這也許是對其內部完成方式的最佳解釋。 問題是,通常在代碼的某些點上,您需要根據異步任務的結果進行同步。 例如:
private void Form1_Load(object sender, EventArgs e)
{
Task<bool> task = WaitForItToWork();
task.ContinueWith(_ => {
MessageBox.Show("WaitForItToWork done:" + task.Result.toString()); // true or false
}, TaskScheduler.FromCurrentSynchronizationContext());
}
您可以簡單地做到這一點:
private async void Form1_Load(object sender, EventArgs e)
{
bool result = await WaitForItToWork();
MessageBox.Show("WaitForItToWork done:" + result.toString()); // true or false
}
但是,那也會使Form1_Load
成為異步方法。
[更新]
下面是我嘗試說明在這種情況下async/await
實際執行的操作。 我創建了兩個具有相同邏輯的版本, WaitForItToWorkAsync
(使用async/await
)和WaitForItToWorkAsyncTap
(使用不帶async/await
TAP模式 )。 與第二個版本不同,第一個版本非常瑣碎。 因此,盡管async/await
在很大程度上是編譯器的語法糖,但它使異步代碼更易於編寫和理解。
// fake outcome() method for testing
bool outcome() { return new Random().Next(0, 99) > 50; }
// with async/await
async Task<bool> WaitForItToWorkAsync()
{
var succeeded = false;
while (!succeeded)
{
succeeded = outcome(); // if it worked, make as succeeded, else retry
await Task.Delay(1000);
}
return succeeded;
}
// without async/await
Task<bool> WaitForItToWorkAsyncTap()
{
var context = TaskScheduler.FromCurrentSynchronizationContext();
var tcs = new TaskCompletionSource<bool>();
var succeeded = false;
Action closure = null;
closure = delegate
{
succeeded = outcome(); // if it worked, make as succeeded, else retry
Task.Delay(1000).ContinueWith(delegate
{
if (succeeded)
tcs.SetResult(succeeded);
else
closure();
}, context);
};
// start the task logic synchronously
// it could end synchronously too! (e.g, if we used 'Task.Delay(0)')
closure();
return tcs.Task;
}
// start both tasks and handle the completion of each asynchronously
private void StartWaitForItToWork()
{
WaitForItToWorkAsync().ContinueWith((t) =>
{
MessageBox.Show("WaitForItToWorkAsync complete: " + t.Result.ToString());
}, TaskScheduler.FromCurrentSynchronizationContext());
WaitForItToWorkAsyncTap().ContinueWith((t) =>
{
MessageBox.Show("WaitForItToWorkAsyncTap complete: " + t.Result.ToString());
}, TaskScheduler.FromCurrentSynchronizationContext());
}
// await for each tasks (StartWaitForItToWorkAsync itself is async)
private async Task StartWaitForItToWorkAsync()
{
bool result = await WaitForItToWorkAsync();
MessageBox.Show("WaitForItToWorkAsync complete: " + result.ToString());
result = await WaitForItToWorkAsyncTap();
MessageBox.Show("WaitForItToWorkAsyncTap complete: " + result.ToString());
}
關於線程的幾句話 。 這里沒有明確創建其他線程。 在內部, Task.Delay()
實現可能使用池線程(我懷疑它們使用Timer Queues ),但是在此特定示例(一個WinForms應用程序)中, await
后的繼續將發生在同一UI線程上。 在其他執行環境(例如控制台應用程序)中,它可能會在其他線程上繼續運行。 IMO,Stephen Cleary 撰寫的這篇文章是理解async/await
線程概念的必讀文章 。
如果任務是異步的,則可以嘗試:
async Task WaitForItToWork()
{
await Task.Run(() =>
{
bool succeeded = false;
while (!succeeded)
{
// do work
succeeded = outcome; // if it worked, make as succeeded, else retry
System.Threading.Thread.Sleep(1000); // arbitrary sleep
}
});
}
只是提供另一個解決方案
public static void WaitForCondition(Func<bool> predict)
{
Task.Delay(TimeSpan.FromMilliseconds(1000)).ContinueWith(_ =>
{
var result = predict();
// the condition result is false, and we need to wait again.
if (result == false)
{
WaitForCondition(predict);
}
});
}
您實際上並不需要WaitItForWork
方法,只需等待數據庫初始化任務即可:
async Task Run()
{
await InitializeDatabase();
// Do what you need after database is initialized
}
async Task InitializeDatabase()
{
// Perform database initialization here
}
如果您有多個調用WaitForItToWork
的代碼,則需要將數據庫初始化包裝到Task
,並在所有工作程序中等待它,例如:
readonly Task _initializeDatabaseTask = InitializeDatabase();
async Task Worker1()
{
await _initializeDatabaseTask;
// Do what you need after database is initialized
}
async Task Worker2()
{
await _initializeDatabaseTask;
// Do what you need after database is initialized
}
static async Task InitializeDatabase()
{
// Initialize your database here
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.