[英]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.