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

Update a UI state when all Async methods in different presenters are complete

我有一个关于下面代码的优雅和最佳实践的问题。 应用的概念:

  1. 我有一个调用用户控件的主窗体
  2. 这个主窗体有一个进度控件,通过根据需要附加到用户控件的事件处理程序向用户显示多个任务的进度
  3. View(用户控件)"ViewPhase1"有进度变化事件
  4. 此视图有两个演示者
  5. 这些演示者中的每一个都有异步任务
  6. 当任务即将开始时进度发生变化
  7. 当任务结束时,我检查两个任务是否都已完成,如果是,进度设置为 100%(完成)

因为我有两个独立的演示者调用异步方法,其中一个可以先于另一个完成,所以我在视图中创建了两个属性 "DoneTasksWork" 以便能够在演示者中允许知道是否每个 Presenter 的任务都已完成。 如果两者都完成,则 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();
        }
    }
}

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

您究竟希望在不使用 await 的情况下调用 Task.WhenAll(task1, task2); 会发生什么? 我会告诉你:它直接运行并会在 _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(); 方法,因为你也不能在构造函数中执行此操作。

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