C# 在 GUI 线程上等待并捕获异常 WPF
C# await on GUI thread and catch exceptions WPF
我正在使用 MVVM Light,并且有一个用于显示对话框的接口 IDialogService。此接口已在 App.xaml.cs
中实现
一个具体的方法很有意思:
Task<bool> ShowMessage(string message, string title, string buttonConfirmText, string buttonCancelText, Action<bool> afterHideCallback);
方法实现为:
public Task<bool> ShowMessage(string message, string title, string buttonConfirmText,
string buttonCancelText,
Action<bool> afterHideCallback)
{
return Task.Factory.StartNew(() =>
{
var style = new Style(typeof(MessageBox));
style.Setters.Add(new Setter(MessageBox.OkButtonContentProperty, buttonConfirmText));
style.Setters.Add(new Setter(MessageBox.CancelButtonContentProperty, buttonCancelText));
var result = MessageBox.Show(_GetActiveWindow(), message, title,
MessageBoxButton.OKCancel,
MessageBoxImage.Question, style) == MessageBoxResult.OK;
if (afterHideCallback != null) afterHideCallback(result);
return result;
其中 _currentTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
定义在 OnStartup
所以通常我们应该在前面调用这个方法来获取布尔值:
var result = await DialogService.ShowMessage(
Resources.Areyousure,Resources.Warning,
Resources.Yes, Resources.No, null);
到目前为止一切顺利。现在我有一个包装器方法来执行代码并捕获异常,然后显示错误消息框。
public bool TryCatchExecution(Action action, string successMessage = null)
{
try
{
action();
if (!string.IsNullOrEmpty(successMessage))
DialogService.ShowMessage(successMessage, Resources.Success);
return true;
}
catch (LogException ex)
{
DialogService.ShowError(ex.Error.LogMessage, Resources.Error, Resources.OK, null);
}
catch (Exception ex)
{
DialogService.ShowError(ex.Message, Resources.Error, Resources.OK, null);
}
return false;
}
现在我遇到了一个问题。如果我像示例 A 那样使用,GUI 线程将在 var result = DialogService.ShowMessage
行被阻塞。但是,如果我像示例 B 中那样使用,GUI 线程不会被阻塞,会显示消息框,并且一切正常。直到我得到一个例外。异常未被代码处理。错误是 "A first chance exception of type 'System.ServiceModel.FaultException`1' occurred in mscorlib.dll" 并且应用程序崩溃。正如我一直在阅读的那样,这与 SynchronizationContext 有关。
//Sample A
private void ExecuteDeleteCommand()
{
TryCatchExecution(() =>
{
var result = DialogService.ShowMessage(
Resources.Areyousure,
Resources.Warning,
Resources.Yes,
Resources.No, null).Result;
if (!result) return;
_datalayer.DeleteField(FieldSelected);
Refresh();
FieldEdit = new MsgSqlFieldMapping();
RaisePropertyChanged("SqlRepository");
DialogService.ShowMessage(Resources.OperationSucceeded, Resources.Success);
});
}
//Sample B
private void ExecuteDeleteCommand()
{
TryCatchExecution(async () =>
{
var result =await DialogService.ShowMessage(
Resources.Areyousure,
Resources.Warning,
Resources.Yes,
Resources.No, null);
if (!result) return;
_datalayer.DeleteField(FieldSelected);
Refresh();
FieldEdit = new MsgSqlFieldMapping();
RaisePropertyChanged("SqlRepository");
await DialogService.ShowMessage(Resources.OperationSucceeded, Resources.Success);
});
}
请帮助我了解这里发生了什么以及如何处理它。
THNX 很多。
您的问题是由于 async void
- 具体来说,通过将 async
lambda 作为类型 Action
的参数传递,您正在创建一个 async void
方法。 problems with async void
methods 之一是您无法捕获异常(至少,不是正常方式)。
要解决此问题,请创建一个采用 async equivalent of Action
, which is Func<Task>
:
的辅助方法的重载
public async Task<bool> TryCatchExecution(Func<Task> action, string successMessage = null)
{
try
{
await action();
if (!string.IsNullOrEmpty(successMessage))
DialogService.ShowMessage(successMessage, Resources.Success);
return true;
}
catch (LogException ex)
{
DialogService.ShowError(ex.Error.LogMessage, Resources.Error, Resources.OK, null);
}
catch (Exception ex)
{
DialogService.ShowError(ex.Message, Resources.Error, Resources.OK, null);
}
return false;
}
我正在使用 MVVM Light,并且有一个用于显示对话框的接口 IDialogService。此接口已在 App.xaml.cs
中实现一个具体的方法很有意思:
Task<bool> ShowMessage(string message, string title, string buttonConfirmText, string buttonCancelText, Action<bool> afterHideCallback);
方法实现为:
public Task<bool> ShowMessage(string message, string title, string buttonConfirmText,
string buttonCancelText,
Action<bool> afterHideCallback)
{
return Task.Factory.StartNew(() =>
{
var style = new Style(typeof(MessageBox));
style.Setters.Add(new Setter(MessageBox.OkButtonContentProperty, buttonConfirmText));
style.Setters.Add(new Setter(MessageBox.CancelButtonContentProperty, buttonCancelText));
var result = MessageBox.Show(_GetActiveWindow(), message, title,
MessageBoxButton.OKCancel,
MessageBoxImage.Question, style) == MessageBoxResult.OK;
if (afterHideCallback != null) afterHideCallback(result);
return result;
其中 _currentTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
定义在 OnStartup
所以通常我们应该在前面调用这个方法来获取布尔值:
var result = await DialogService.ShowMessage(
Resources.Areyousure,Resources.Warning,
Resources.Yes, Resources.No, null);
到目前为止一切顺利。现在我有一个包装器方法来执行代码并捕获异常,然后显示错误消息框。
public bool TryCatchExecution(Action action, string successMessage = null)
{
try
{
action();
if (!string.IsNullOrEmpty(successMessage))
DialogService.ShowMessage(successMessage, Resources.Success);
return true;
}
catch (LogException ex)
{
DialogService.ShowError(ex.Error.LogMessage, Resources.Error, Resources.OK, null);
}
catch (Exception ex)
{
DialogService.ShowError(ex.Message, Resources.Error, Resources.OK, null);
}
return false;
}
现在我遇到了一个问题。如果我像示例 A 那样使用,GUI 线程将在 var result = DialogService.ShowMessage
行被阻塞。但是,如果我像示例 B 中那样使用,GUI 线程不会被阻塞,会显示消息框,并且一切正常。直到我得到一个例外。异常未被代码处理。错误是 "A first chance exception of type 'System.ServiceModel.FaultException`1' occurred in mscorlib.dll" 并且应用程序崩溃。正如我一直在阅读的那样,这与 SynchronizationContext 有关。
//Sample A
private void ExecuteDeleteCommand()
{
TryCatchExecution(() =>
{
var result = DialogService.ShowMessage(
Resources.Areyousure,
Resources.Warning,
Resources.Yes,
Resources.No, null).Result;
if (!result) return;
_datalayer.DeleteField(FieldSelected);
Refresh();
FieldEdit = new MsgSqlFieldMapping();
RaisePropertyChanged("SqlRepository");
DialogService.ShowMessage(Resources.OperationSucceeded, Resources.Success);
});
}
//Sample B
private void ExecuteDeleteCommand()
{
TryCatchExecution(async () =>
{
var result =await DialogService.ShowMessage(
Resources.Areyousure,
Resources.Warning,
Resources.Yes,
Resources.No, null);
if (!result) return;
_datalayer.DeleteField(FieldSelected);
Refresh();
FieldEdit = new MsgSqlFieldMapping();
RaisePropertyChanged("SqlRepository");
await DialogService.ShowMessage(Resources.OperationSucceeded, Resources.Success);
});
}
请帮助我了解这里发生了什么以及如何处理它。
THNX 很多。
您的问题是由于 async void
- 具体来说,通过将 async
lambda 作为类型 Action
的参数传递,您正在创建一个 async void
方法。 problems with async void
methods 之一是您无法捕获异常(至少,不是正常方式)。
要解决此问题,请创建一个采用 async equivalent of Action
, which is Func<Task>
:
public async Task<bool> TryCatchExecution(Func<Task> action, string successMessage = null)
{
try
{
await action();
if (!string.IsNullOrEmpty(successMessage))
DialogService.ShowMessage(successMessage, Resources.Success);
return true;
}
catch (LogException ex)
{
DialogService.ShowError(ex.Error.LogMessage, Resources.Error, Resources.OK, null);
}
catch (Exception ex)
{
DialogService.ShowError(ex.Message, Resources.Error, Resources.OK, null);
}
return false;
}