WPF的回调问题

时间:2015-03-02 19:16:39

标签: c# .net wpf asynchronous callback

我在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 ...

就这样吧。 有没有办法让这段代码更容易阅读? 谢谢。

1 个答案:

答案 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线程中执行所有错误处理。

您可以在整个应用程序中执行相同的操作。而不是将所有放入后台线程然后显式编组,而只是在后台线程中执行操作,该后台线程应该在后台线程中完全执行