在不同演示者中的所有异步方法完成时更新UI状态

时间:2016-10-04 13:27:29

标签: c# async-await mvp

我对下面的代码的优雅和最佳实践有疑问。 应用程序的概念:

  1. 我有一个调用用户控件的主窗体
  2. 此主窗体有一个进度控件,可根据需要通过附加到用户控件的事件处理程序向用户显示多个任务的进度
  3. 视图(用户控件)“ViewPhase1”具有更改进度的事件
  4. 此视图有两位演示者
  5. 每位演示者都有异步任务
  6. 任务即将开始进度时更改
  7. 当任务结束时,我检查两项任务是否都已完成,如果进度设置为100%(已完成)
  8. 因为我有两个独立的演示者调用异步方法,其中一个可以在另一个之前完成,所以我在视图中创建了两个属性“DoneTasksWork”,以便能够在演示者中知道每个演示者的任务是否完成完成了。 如果两者都完成,那么UI中的进度设置为100%(并启用其中的所有控件),如果没有进度将保持仍在运行。

    这是一个优雅的解决方案吗?考虑到这种情况,只有当完成状态的进度控制在不同类中的异步方法完成时,我可以使用另一个解决方案而不是用作标志的布尔属性吗?

    谢谢!

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using Project.Utilities;
    
    namespace Project
    {
        public partial class ViewPhase1 : UserControl, IViewPhase1Work1, IViewPhase1Work2
        {
            private PresenterPhase1Work1 _presenterPhase1Work1;
            private PresenterPhase1Work2 _presenterPhase1Work2;
    
            public ViewPhase1()
            {
                InitializeComponent();
                _presenterPhase1Work1 = new PresenterPhase1Work1(this);
                _presenterPhase1Work2 = new PresenterPhase1Work2(this);
            }
    
            /// <summary>
            /// This event is listened by form that invokes this user control and updates progress control.
            /// </summary>
            public event ProgressChangedEventHandler ProgressChangedEvent;
            public void OnProgressHandler(object sender, ProgressChangedEventArgs e)
            {
                this.Invoke((Action)delegate
                {
                    if (this.ProgressChangedEvent != null)
                    {
                        ProgressChangedEvent(this, e);
                    }
                });
            }
            public void ShowException(string exMessage, MessageBoxButtons bts, MessageBoxIcon icon)
            {
                MessageBox.Show("", exMessage, bts, icon);
            }
            public DataGridView GridInvoices { get; set; }
            public DataGridView GridReceipts { get; set; }
            public DataGridView GridProducts { get; set; }
            public DataGridView GridCustomers { get; set; }
            bool IViewPhase1Work1.DoneTasksWork { get; set; }
            bool IViewPhase1Work2.DoneTasksWork { get; set; }
        }
    
        public interface IViewProgress
        {
            event ProgressChangedEventHandler ProgressChangedEvent;
            void OnProgressHandler(object sender, ProgressChangedEventArgs e);
        }
    
        public interface IViewPhase1Work1 : IViewProgress, IViewException
        {
            bool DoneTasksWork { get; set; }
            DataGridView GridProducts { get; set; }
            DataGridView GridCustomers { get; set; }
        }
    
        public interface IViewPhase1Work2 : IViewProgress, IViewException
        {
            bool DoneTasksWork { get; set; }
            DataGridView GridInvoices { get; set; }
            DataGridView GridReceipts { get; set; }
        }
    
        public interface IViewException
        {
            void ShowException(string exMessage, MessageBoxButtons bts, MessageBoxIcon icon);
        }
    
        public class PresenterPhase1Work1
        {
            private readonly IViewPhase1Work1 _view;
    
            public PresenterPhase1Work1(IViewPhase1Work1 view)
            {
                _view = view;
                GetInformation();
            }
    
            private void GetInformation()
            {
                try
                {
                    var task1 = Task.Run(() => GetDataProducts());
                    var task2 = Task.Run(() => GetDataCustomers());
                    Task.WhenAll(task1, task2);
    
                    _view.GridProducts.DataSource = task1.Result;
                    _view.GridCustomers.DataSource = task2.Result;
                }
                catch (Exception ex)
                {
                    _view.ShowException(ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
    
                finally
                {
                    _view.DoneTasksWork = true;
                    _view.OnProgressHandler(this, new ProgressChangedEventArgs(_view.DoneTasksWork && _view.DoneTasksWork ? 100 : 50, _view.DoneTasksWork && _view.DoneTasksWork ? "Done" : "Getting data"));
                }
            }
    
            private async Task<object> GetDataCustomers()
            {
                return await Util.GetDataCustomerAsync();
            }
            private async Task<object> GetDataProducts()
            {
                return await Util.GetDataProductsAsync();
            }
        }
    
        public class PresenterPhase1Work2
        {
            private readonly IViewPhase1Work2 _view;
    
            public PresenterPhase1Work2(IViewPhase1Work2 view)
            {
                _view = view;
                GetInformation();
            }
    
            private void GetInformation()
            {
                try
                {
                    var task1 = Task.Run(() => GetDataInvoices());
                    var task2 = Task.Run(() => GetDataReceipts());
                    Task.WhenAll(task1, task2);
    
                    _view.GridInvoices.DataSource = task1.Result;
                    _view.GridReceipts.DataSource = task2.Result;
                }
                catch (Exception ex)
                {
                    _view.ShowException(ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
    
                finally
                {
                    _view.DoneTasksWork = true;
                    _view.OnProgressHandler(this, new ProgressChangedEventArgs(_view.DoneTasksWork && _view.DoneTasksWork ? 100 : 50, _view.DoneTasksWork && _view.DoneTasksWork ? "Done" : "Getting data"));
                }
            }
    
            private async Task<object> GetDataInvoices()
            {
                return await Util.GetDataInvoicesAsync();
            }
    
            private async Task<object> GetDataReceipts()
            {
                return await Util.GetDataReceiptsAsync();
            }
        }
    }
    

1 个答案:

答案 0 :(得分:1)

优雅的解决方案不是。让我解释一下。

您希望在不使用Task.WhenAll(task1, task2);的情况下调用await会发生什么? 我会告诉你:它直接运行并将阻塞_view.GridInvoices.DataSource = task1.Result;,然后也可能在下一行。

所以,为了解决这个方法(在PresenterPhase1Work1中,但它也适用于其他演示者):

private void GetInformation()
    {
        try
        {
            var task1 = Task.Run(() => GetDataProducts());
            var task2 = Task.Run(() => GetDataCustomers());
            Task.WhenAll(task1, task2);

            _view.GridProducts.DataSource = task1.Result;
            _view.GridCustomers.DataSource = task2.Result;
        }
        catch (Exception ex)
        {
            _view.ShowException(ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        finally
        {
            _view.DoneTasksWork = true;
            _view.OnProgressHandler(this, new ProgressChangedEventArgs(_view.DoneTasksWork && _view.DoneTasksWork ? 100 : 50, _view.DoneTasksWork && _view.DoneTasksWork ? "Done" : "Getting data"));
        }
    }

可以更改为:

private async Task GetInformationAsync()
    {
        try
        {
            var task1 = Task.Run(() => GetDataProducts());
            var task2 = Task.Run(() => GetDataCustomers());

            await Task.WhenAny(task1, task2);
            _view.OnProgressHandler(this, new ProgressChangedEventArgs(50, "Getting data"));

            await Task.WhenAll(task1, task2);
            _view.OnProgressHandler(this, new ProgressChangedEventArgs(100, "Done"));

            _view.GridProducts.DataSource = await task1;
            _view.GridCustomers.DataSource = await task2;
        }
        catch (Exception ex)
        {
            _view.ShowException(ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

现在,在构造函数中调用异步方法是个坏主意。构造函数不允许使用async关键字。所以你必须重新设计你的课程。所以我会将GetInformation方法设为public并调用并等待视图中其他位置的方法。在您的视图中调用await presenterPhase1Work1.GetMethodAsync();后,您立即知道该项工作已完成。所以不再需要这个布尔标志。

这确实意味着您的ViewPhase1需要另一种方法来调用 await presenterPhase1Work1.GetMethodAsync();方法,因为你也不能在构造函数中执行此操作。

在构造函数中等待完成数据加载从来都不是一个好主意恕我直言,无论是否在后台线程中完成。