簡體   English   中英

異步數據加載和后續錯誤處理

[英]Asynchronous data loading and subsequent error handling

我有一個涉及數據庫的應用程序。 以前,在打開窗口時,我將查詢數據庫並使用它來填充我的視圖模型的各個方面。 這工作得相當不錯,但是當數據訪問花費的時間比預期的長時,可能會導致明顯的暫停。

當然,自然的解決方案是異步運行數據庫查詢,然后在查詢完成時填充視圖模型。 這不是很難,但是它提出了一些有關錯誤處理的有趣問題。

以前,如果數據庫查詢出了問題(這確實是一個很大的問題),我將通過視圖模型構造函數傳播該異常,最終使該異常返回給想要打開窗口的調用方。 然后,它可能會顯示適當的錯誤,而實際上沒有打開窗口。

但是,現在,該窗口立即打開,然后在查詢完成時填充。 現在的問題是,我應該在什么時候檢查后台任務中的錯誤? 該窗口已經打開,因此行為需要有所不同,但是有什么干凈的方法可以向用戶指示失敗並允許正常恢復/關閉?

供參考,以下是演示基本模式的代碼段:

    public ViewModel()
    {
        _initTask = InitAsync();
        //Now where do I check on the status of the init task?
    }

    private async Task InitAsync()
    {
        //Do stuff...
    }

    //....

    public void ShowWindow()
    {
        var vm = new ViewModel(); //Previously this could throw an exception that would prevent window from being shown
        _windowServices.Show(vm);
    }

我考慮過的一個選項是使用異步工廠方法構造ViewModel,從而允許在嘗試顯示窗口之前構造並初始化整個對象。 這保留了在打開窗口之前報告錯誤的舊方法。 但是,它放棄了通過這種方法獲得的一些UI響應能力,這使得窗口的初始加載與查詢並行進行,並且還允許我(在某些情況下)隨着每個查詢的完成以增量方式更新UI。而不是讓UI一次組成自己。 它避免了鎖定UI線程,但是並沒有減少用戶實際看到該窗口並可以開始與之交互的時間。

也許在視圖模型和基礎服務之間使用某種消息傳遞/介體?

使用MVVMLight的半偽代碼

public ViewModel()
{
    Messenger.Default.Register<NotificationMessage<Exception>>(this, message =>
        {
            // Handle here
        });

    Task.Factory.StartNew(() => FetchData());
}

public async Task FetchData()
{
    // Some magic happens here
    try
    {
        Thread.Sleep(2000);
        throw new ArgumentException();
    }
    catch (Exception e)
    {
        Messenger.Default.Send(new NotificationMessage<Exception>(this, e, "Aw snap!"));
    }
}

在這里處理過類似的問題。 我發現最好從任務內部引發一個錯誤事件,如下所示:

// ViewModel

public class TaskFailedEventArgs: EventArgs
{
    public Exception Exception { get; private set; }
    public bool Handled { get; set; }

    public TaskFailedEventArgs(Exception ex) { this.Exception = ex; }
}

public event EventHandler<TaskFailedEventArgs> TaskFailed = delegate { };

public ViewModel()
{
    this.TaskFailed += (s, e) =>
    {
        // handle it, e.g.: retry, report or set a property

        MessageBox.Show(e.Exception.Message);
        e.Handled = true;
    };

    _initTask = InitAsync();
    //Now where do I check on the status of the init task?
}

private async Task InitAsync()
{
    try
    {
        // do the async work
    }
    catch (Exception ex)
    {
        var args = new TaskFailedEventArgs(ex);
        this.TaskFailed(this, args);
        if (!args.Handled)
            throw;
    }
}

// application

public void ShowWindow()
{
    var vm = new ViewModel(); //Previously this could throw an exception that would prevent window from being shown

    _windowServices.Show(vm);
}

該窗口仍然顯示,但是它應該顯示某種進度通知(例如,使用IProgress<T> pattern ),直到操作結束(如果失敗則顯示錯誤信息)。

在錯誤事件處理程序中,您可以為用戶提供一個選項,以根據您的業務邏輯優雅地重試或退出應用程序。

Stephen Cleary他的博客上有一系列有關Async OOP的文章 特別是關於構造函數

暫無
暫無

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

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