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