簡體   English   中英

等待任務完成而不阻塞 UI 線程

[英]Wait for Task to Complete without Blocking UI Thread

我有一個相當復雜的 WPF 應用程序(很像 VS2013)在應用程序的主 shell 中停靠了IDocumentsITools 當主窗口關閉時,需要安全關閉這些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);
}

在我看來,使用這種方法有以下好處:

  • 更容易理解和理解
  • 更簡單的異常處理

筆記:

  • 我在代碼片段中省略了取消標記,如果您願意,可以輕松添加。
  • .net framework 4.5 和 c# 5.0 之后存在 async/await 關鍵字

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM