最佳异步while方法
本文关键字:方法 while 异步 最佳 | 更新日期: 2023-09-27 18:29:55
我需要编写一些异步代码,这些代码本质上试图重复地与数据库对话并初始化数据库。第一次尝试往往会失败,因此需要重试。
在过去的日子里,我会使用类似于的模式
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
}
});
}
请参阅http://msdn.microsoft.com/en-us/library/hh195051.aspx.
只需提供另一个解决方案
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
}