[英]Wait for Task to Complete without Blocking UI Thread
我有一个相当复杂的 WPF 应用程序(很像 VS2013)在应用程序的主 shell 中停靠了IDocuments
和ITools
。 当主窗口关闭时,需要安全关闭这些Tools
之一,以避免进入“坏”状态。 所以我使用 Caliburn Micro 的public override void CanClose(Action<bool> callback)
方法来执行一些数据库更新等。我public override void CanClose(Action<bool> callback)
的问题是这个方法中的所有更新代码都使用 MongoDB Driver 2.0 而这些东西是async
。 一些代码; 目前我正在尝试执行
public override void CanClose(Action<bool> callback)
{
if (BackTestCollection.Any(bt => bt.TestStatus == TestStatus.Running))
{
using (ManualResetEventSlim tareDownCompleted = new ManualResetEventSlim(false))
{
// Update running test.
Task.Run(async () =>
{
StatusMessage = "Stopping running backtest...";
await SaveBackTestEventsAsync(SelectedBackTest);
Log.Trace(String.Format(
"Shutdown requested: saved backtest \"{0}\" with events",
SelectedBackTest.Name));
this.source = new CancellationTokenSource();
this.token = this.source.Token;
var filter = Builders<BsonDocument>.Filter.Eq(
BackTestFields.ID, DocIdSerializer.Write(SelectedBackTest.Id));
var update = Builders<BsonDocument>.Update.Set(BackTestFields.STATUS, TestStatus.Cancelled);
IMongoDatabase database = client.GetDatabase(Constants.DatabaseMappings[Database.Backtests]);
await MongoDataService.UpdateAsync<BsonDocument>(
database, Constants.Backtests, filter, update, token);
Log.Trace(String.Format(
"Shutdown requested: updated backtest \"{0}\" status to \"Cancelled\"",
SelectedBackTest.Name));
}).ContinueWith(ant =>
{
StatusMessage = "Disposing backtest engine...";
if (engine != null)
engine.Dispose();
Log.Trace("Shutdown requested: disposed backtest engine successfully");
callback(true);
tareDownCompleted.Set();
});
tareDownCompleted.Wait();
}
}
}
现在,首先我没有ManualResetEventSlim
,这显然会在我更新后台 [thread-pool] 线程上的数据库之前返回到CanClose
调用者。 为了在我完成更新之前阻止返回,我试图阻止返回,但这会冻结 UI 线程并阻止任何事情发生。
如何在不过早返回调用者的情况下运行我的清理代码?
感谢您的时间。
请注意,我无法使用异步签名覆盖OnClose
方法,因为调用代码不会等待它(我无法控制它)。
我认为除了阻止返回之外,您别无选择。 但是,尽管 UI 线程被锁定,您的更新仍应运行。 我不会使用 ManualResetEventSlim,而只是一个简单的 wait() 和一个没有继续的任务。 原因是默认情况下 Task.Run 会阻止子任务(您的延续)附加到父任务,因此您的延续可能没有时间在窗口关闭之前完成,请参阅此帖子。
public override void CanClose(Action<bool> callback)
{
if (BackTestCollection.Any(bt => bt.TestStatus == TestStatus.Running))
{
// Update running test.
var cleanupTask = Task.Run(async () =>
{
StatusMessage = "Stopping running backtest...";
await SaveBackTestEventsAsync(SelectedBackTest);
// other cleanup tasks
// No continuation
StatusMessage = "Disposing backtest engine...";
if (engine != null)
engine.Dispose();
Log.Trace("Shutdown requested: disposed backtest engine successfully");
callback(true);
});
cleanupTask.Wait();
}
}
如果您确实需要使用延续,您还可以将 TaskFactory.StartNew 与 TaskCreationOptions.AttachedToParent 一起使用。
您可以使用类似于 WinForm 的Application.DoEvents
东西,但对于 WPF,它涉及使用标志,触发您的任务,而不是Wait
它,而是在循环中不断处理 UI 消息,直到您的任务完成并设置标志。 例如:
if (BackTestCollection.Any(bt => bt.TestStatus == TestStatus.Running))
{
bool done = false;
// Update running test.
Task.Run(async () =>
{
StatusMessage = "Stopping running backtest...";
await SaveBackTestEventsAsync(SelectedBackTest);
Log.Trace(String.Format(
"Shutdown requested: saved backtest \"{0}\" with events",
SelectedBackTest.Name));
this.source = new CancellationTokenSource();
this.token = this.source.Token;
var filter = Builders<BsonDocument>.Filter.Eq(
BackTestFields.ID, DocIdSerializer.Write(SelectedBackTest.Id));
var update = Builders<BsonDocument>.Update.Set(BackTestFields.STATUS, TestStatus.Cancelled);
IMongoDatabase database = client.GetDatabase(Constants.DatabaseMappings[Database.Backtests]);
await MongoDataService.UpdateAsync<BsonDocument>(
database, Constants.Backtests, filter, update, token);
Log.Trace(String.Format(
"Shutdown requested: updated backtest \"{0}\" status to \"Cancelled\"",
SelectedBackTest.Name));
StatusMessage = "Disposing backtest engine...";
if (engine != null)
engine.Dispose();
Log.Trace("Shutdown requested: disposed backtest engine successfully");
callback(true);
done = true;
});
while (!done)
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
}
这有点 hacky,但考虑到您的情况并且无法控制调用代码,这可能是您保持响应式 UI 而不立即返回给调用者的唯一选择。
我尝试了 async/await 组合来解决这种问题。 首先我们将sync void CanClose 转换为async void。 然后 async void 方法调用 async Task 方法来完成工作。 我们必须这样做,因为在捕获异常时存在 async void 的危险。
public override async void CanClose(Action<bool> callback)
{
await CanCloseAsync(callback);
}
public async Task CanCloseAsync(Action<bool> callback)
{
var result1 = await DoTask1();
if (result1)
await DoTask2();
callback(result1);
}
在我看来,使用这种方法有以下好处:
笔记:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.