我在WPF .Net 4.5中遇到了异步编程的回调问题。 应该有一种方法以更容易理解的方式执行此代码(我已经压缩了它的很大一部分,以便更容易看到发生了什么)。
我认为没有办法简单地删除代码,因为我需要调用Dispatcher来操纵WPF控件和TbSequence.Focus()
和Utils.ShowMessageBox
中的调用。
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
try
{
Controller.Busy = true;
System.Threading.Tasks.Task.Run(() =>
{
try
{
Controller.SaveItem();
}
catch (BdlDbException ex)
{
if (ex.ExceptionSubType == DbExceptionSubTypes.UniqueViolation)
{
HandleUniqueViolation(ex);
}
else
{
string errorMessage = "";
errorMessage = ex.Message;
Dispatcher.BeginInvoke(new Action(() => Utils.ShowMessageBox(t_MessageBox.Attention, errorMessage)));
}
}
Controller.Busy = false;
});
}
catch (FieldException ex)
{
if (ex.FieldName == "FirstName")
{
TbFirstName.ValidationError = true;
TbFirstName.ApplyErrorToolTip(ex.Message);
}
}
}
public void Init(UcEdit container, Employee entity = null)
{
Controller.Busy = true;
System.Threading.Tasks.Task.Run(() =>
{
try
{
Controller.Init(entity);
}
catch (BdlEntryNotFoundException ex)
{
HandleNotFoundException(ex);
}
Container.Dispatcher.BeginInvoke(new Action(() =>
{
Container.DataContext = Controller;
// Instructions order for focusing TbSequence after load should be different in case we have an existent item
// because we have to focus the control AFTER it is filled with data, in order to set the caret position correctly.
if (Controller.IsNewItem)
{
this.DataContext = Controller;
TbSequence.Focus();
Controller.Busy = false;
}
else
{
TbSequence.TextChanged += TbSequence_TextChanged;
this.DataContext = Controller;
SetButtons();
}
}));
});
}
private void HandleUniqueViolation(BdlDbException ex)
{
string errorMessage = "";
bool isSequence = false; // if true, it's name.
if (ex.Fields[1] == "Sequence")
{
errorMessage = "There is already an Employee with the sequence \"" + Controller.Item.Sequence + "\".";
isSequence = true;
}
else
{
errorMessage = "There is already an Employee named \"" + Controller.Item.FirstName +
" " + Controller.Item.LastName + "\".";
}
errorMessage += "\r\nLoad it from Database?\r\n(All the changes on this window will be lost.)";
Dispatcher.BeginInvoke(new Action(() =>
{
MessageBoxResult res = Utils.ShowMessageBox(t_MessageBox.Question, errorMessage, MessageBoxButton.YesNo);
switch (res)
{
case MessageBoxResult.Yes:
if (isSequence)
{
System.Threading.Tasks.Task.Run(() =>
{
Controller.GetEmployeeBySequence(Controller.Item.Sequence);
Init(Container, Controller.OriginalItem);
});
}
else
{
System.Threading.Tasks.Task.Run(() =>
{
Controller.GetEmployeeByName(Controller.Item.FirstName, Controller.Item.LastName);
Init(Container, Controller.OriginalItem);
});
}
break;
case MessageBoxResult.No:
break;
}
}));
}
正如您所看到的,这里有一个主要的回调问题,其行为如下:
Save_Executed (UI) -> HandleUniqueViolation (Task) -> ShowMessageBox (UI) -> Controller.GetEmployeeBySequence (Task) -> Controller.Init ...
就这样吧。 有没有办法让这段代码更容易阅读? 谢谢。
答案 0 :(得分:1)
您通过在Task.Run
的调用中放置(或多或少)整个方法体,然后在该回调中明确地编组到UI线程来启动代码。
缩小Task.Run
来电的范围,以便您的用户界面代码不在Run
的调用范围内,而不是在Invoke
的调用内:
private async void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
try
{
Controller.Busy = true;
try
{
await Task.Run(() => Controller.SaveItem());
}
catch (BdlDbException ex)
{
if (ex.ExceptionSubType == DbExceptionSubTypes.UniqueViolation)
{
HandleUniqueViolation(ex);
}
else
{
string errorMessage = "";
errorMessage = ex.Message;
Utils.ShowMessageBox(t_MessageBox.Attention, errorMessage);
}
}
Controller.Busy = false;
}
catch (FieldException ex)
{
if (ex.FieldName == "FirstName")
{
TbFirstName.ValidationError = true;
TbFirstName.ApplyErrorToolTip(ex.Message);
}
}
}
这里,您正在运行线程池线程中的实际长时间运行业务操作,但在UI线程中执行所有错误处理。
您可以在整个应用程序中执行相同的操作。而不是将所有放入后台线程然后显式编组,而只是在后台线程中执行操作,该后台线程应该在后台线程中完全执行。