1

问题:

我正在开发一个应用程序,在该应用程序中进行一些耗时的操作,我应该在带有取消按钮的表单(WinForm)上显示进度条。所以很明显我正在使用 BackgroundWorker 线程。下面是大致模拟我想要实现的代码的代码。

namespace WindowsFormsApplication1
{
    public delegate void SomeDelegateHandler();

    public partial class Form1 : Form
    {
        public event SomeDelegateHandler DoSomeAction;
        BackgroundWorker bgWorker;

        public Form1()
        {
            InitializeComponent();

            bgWorker = new BackgroundWorker();
            bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
        }

        void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            //Some logic code here.
            for (int i = 0; i < 100; i++)
            {
                DoSomeAction();
            }
        }       

        private void Form1_Shown(object sender, EventArgs e)
        {
            if (DoSomeAction != null)
                bgWorker.RunWorkerAsync();
            else throw new EventNotSubscribedException();//Is this a valid style??
        }
    }

    public class EventNotSubscribedException : ApplicationException
    {
       //Some custom code here
    }
}

我的解决方案

根据上面的代码,只要向用户显示表单(OnShown 事件),我就会启动 backgroundworker 线程。这是因为,用户不需要为此发生任何动作。所以 onshown 做耗时的操作工作。但问题是,正如我上面所展示的,主要耗时的工作是在其他类/组件上执行的,它的边界也很紧(遗留代码:无法重构)。因此,我在启动此表单的旧代码类中订阅了事件 DoSomeAction。

疑问/问题:

如上所示抛出异常是否有效?(请在下面阅读我的理由)。

理由:

OnShown 事件会检查事件处理程序对象上的空值。这是因为,为了使这个表单可用,事件必须由订阅者订阅(使用代码),然后只有它才能工作。如果没有,那么表单只是显示并且根本没有记录,并且使用代码可能不知道为什么会这样。使用代码可能假设订阅事件是选项,就像每个按钮单击事件一样。

希望我的帖子清晰易懂。

谢谢和快乐的编码,Zen :)

4

2 回答 2

1

您的意思是您需要向表单的调用者抛出异常吗?是使用 showDialog 还是 Show 调用?

顺便说一句,我不喜欢从事件中生成异常。相反,最好保留它以便它从在 Form 类上设置了某些状态的地方返回。

例如,我更喜欢使用

IsEventSubscribed = false this.Close()

而不是 EventNotSubscribedException

顺便说一句,我可以在代码中看到一个问题,当调用 bgWorker_DoWork 时,您应该检查 DoSomeAction 为 null,否则可能会导致 NullReferenceException。

优选地,

  1. 从 Form_shown 开始运行 RunWorkerAsync
  2. 勾选 DoWork 中的 Delegate to null,如果为 null,则不调用 DoSomeAction,否则调用。
  3. 在 BackgroundWorker 的 RunWorkerCompleted 上,关闭表单。

如果您需要更多信息,请告诉我。

于 2012-05-29T21:14:38.973 回答
1

我建议让消费代码构造 BackgroundWorker 并将其传递给表单的构造函数。您可以在构造函数中进行空测试并回避整个问题。或者,将委托作为构造函数参数。我的意思是,消费代码需要在操作中更改工作人员委托的可能性有多大?


另一种方法是让对话框监控任务,而不是让对话框控制任务(就像你在这里一样)。例如,您可以有这样的界面:

public interface IMonitorableTask {
    void Start();

    event EventHandler<TData> TaskProgress;
}

WhereTData是一种提供更新对话框可能需要的任何信息(例如完成百分比)的类型。

这样做的缺点是每个任务都需要有自己的类型。这会导致非常丑陋、混乱的代码。你可以通过创建一个帮助类来缓解这个问题,比如:

public class DelegateTask : IMonitorableTask {
    private Action<Action<TData>> taskDelegate;

    public event EventHandler<TData> TaskProgress;

    public DelegateTask(Action<Action<TData>> taskDelegate) {
        if (taskDelegate == null)
            throw new ArgumentNullException("taskDelegate");

        this.taskDelegate = taskDelegate;
    }

    protected void FireTaskProgress(TData data) {
        var handler = TaskProgress;

        if (handler != null)
            handler(this, data);
    }

    public void Start() {
        taskDelegate(FireTaskProgress);
    }
}

然后你的任务方法变成工厂:

public IMonitorableTask CreateFooTask(object argument) {
    return new DelegateTask(progress => {
        DoStuffWith(argument);

        progress(new TData(0.5));

        DoMoreStuffWith(argument);

        progress(new TData(1));
    });
}

现在您可以轻松(*)支持命令行界面。只需将不同的监视器对象附加到任务的事件。

(*) 当然,这取决于您的 UI/逻辑分离的干净程度。

于 2012-05-30T00:07:35.940 回答