我可以创建一个可以修改用户界面的线程,并且我可以中止吗?

时间:2008-11-19 14:46:32

标签: c# .net user-interface multithreading

我的表单上有一个冗长的用户界面操作,只要触发一个事件就会触发。我不想在操作发生时使用UI块,而是想在另一个线程中执行操作,并在该事件再次触发时中止该线程并重新启动。

但是,为了安全地更改表单上的控件,我需要使用表单的Invoke或BeginInvoke方法。如果我这样做,那么我可以将所有UI操作放在一个函数中:

private delegate void DoUIStuffDelegate(Thing1 arg1, Thing2 arg2);
private void doUIStuff(Thing1 arg1, Thing2 arg2)
{
    control1.Visible = false;
    this.Controls.Add(arg1.ToButton());
    ...
    control100.Text = arg2.ToString();
}

...

private void backgroundThread()
{
    Thing1 arg1 = new Thing1();
    Thing2 arg2 = new Thing2();

    this.Invoke(new DoUIStuffDelegate(doUIStuff), arg1, arg2);
}

Thread uiStuffThread = null;

public void OnEventFired()
{
    if (uiStuffThread != null)
        uiStuffThread.Abort();

    uiStuffThread = new Thread(backgroundThread);
    uiStuffThread.Start();
}

但如果我这样做,那么我就失去了在一个单独的线程中工作的好处。或者,我可以将它们各自放在它们自己的函数中:

private delegate void DoUIStuffLine1Delegate();
private delegate void DoUIStuffLine2Delegate(Thing1 arg1);
...

private delegate void DoUIStuffLine100Delegate(Thing2 arg2);

private void doUIStuffLine1()
{
    control1.Visible = false;
}

private void doUIStuffLine2()
{
    this.Controls.Add(arg1.ToButton());
}

...

private void doUIStuffLine100(Thing2 arg2)
{
    control100.Text = arg2.ToString();
}

...

private void backgroundThread()
{
    Thing1 arg1 = new Thing1();
    Thing2 arg2 = new Thing2();

    this.Invoke(new DoUIStuffLine1Delegate(doUIStuffLine1));
    this.Invoke(new DoUIStuffLine2Delegate(doUIStuffLine2), arg1);
    ...
    this.Invoke(new DoUIStuffLine100Delegate(doUIStuffLine100), arg2);
}

Thread uiStuffThread = null;

public void OnEventFired()
{
    if (uiStuffThread != null)
        uiStuffThread.Abort();

    uiStuffThread = new Thread(backgroundThread);
    uiStuffThread.Start();
}

但这是一个可怕的,无法维护的混乱。有没有办法创建一个可以修改用户界面的线程,我可以中止?所以我可以做这样的事情:

private void doUIStuff()
{
    Thing1 arg1 = new Thing1();
    Thing2 arg2 = new Thing2();

    control1.Visible = false;
    this.Controls.Add(arg1.ToButton());
    ...
    control100.Text = arg2.ToString();
}

Thread uiStuffThread = null;

public void OnEventFired()
{
    if (uiStuffThread != null)
        uiStuffThread.Abort();

    uiStuffThread = this.GetNiceThread(doUIStuff);
    uiStuffThread.Start();
}

无需在表单上禁用跨线程检查?理想情况下,我希望能够在线程或方法上设置一些属性,这些属性单独包装委托中随后在表单线程上调用的所有操作。

4 个答案:

答案 0 :(得分:3)

首先 - 不要禁用跨线程检查...表单具有线程关联性......

第二 - 尽量避免中止线程;它不是很好 - 您应该更喜欢干净关闭(例如BackgroundWorker支持的取消)

一个选项可能是编写一个包装方法:

  • 接受类型代理(因此您可以更简单地调用它)
  • 进行必要的检查(抛出终止和展开的例外)

例如:

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        try {
          Action<Action> update = thingToDo =>
          {
              if (worker.CancellationPending) throw new SomeException();
              this.Invoke(thingToDo);
          };

          //...
          string tmp = "abc"; // long running
          update(() => this.Text = tmp);

          tmp = "def"; // long running
          update(() => textbox1.Text = tmp);
        } catch (SomeException) {
          e.Cancel = true;
        }
    }

这仍然有点凌乱,但可以说比左右中止线程更清晰......

答案 1 :(得分:0)

您可以使用框架2中引入的对象SynchronizationContext来避免使用Invoke。好的,同样的事情,您将一个东西替换为另一个东西,但实际上,它更有效且更健壮。 无论如何,跨线程需要检查,因为如果没有这个,你永远不能访问在另一个线程中创建的控件。

阅读: http://www.codeproject.com/KB/cpp/SyncContextTutorial.aspx http://codingly.com/2008/08/04/invokerequired-invoke-synchronizationcontext/

揭露我的想法的一些代码:

   private Thread workerThread;

        private AsyncOperation operation;

        public event EventHandler SomethingHappened;

        public MySynchronizedClass()
        {
            operation = AsyncOperationManager.CreateOperation(null);

            workerThread = new Thread(new ThreadStart(DoWork));

            workerThread.Start();
        }

        private void DoWork()
        {
            operation.Post(new SendOrPostCallback(delegate(object state)
            {
                EventHandler handler = SomethingHappened;

                if(handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }), null);

            operation.OperationCompleted();
        }

答案 2 :(得分:0)

我在这里回答你,因为评论太短了。 我个人使用BackgroundWorker,当方法完成时,线程被释放。基本上你做的是:

bw = new BackgroundWorkerExtended();
bw.DoWork += (DoWorkEventHandler)work;
bw.WorkerSupportsCancellation = true;
//bw.WorkerReportsProgress = true;
bw.RunWorkerCompleted += (RunWorkerCompletedEventHandler)workCompleted;
//bw.ProgressChanged+=new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerAsync();

如果你使用Post,另一件事就是异步,如果你使用,发送它将是同步的。

要取消: bw.CancelAsync();

答案 3 :(得分:0)

除了BackgroundWorker建议和相关注释之外,BackgroundWorker还会将OnProgress回调编组回UI线程,因此您可以从那里更新内容而无需执行Invoke()。

我无法确定这是否有用,无论如何,如果没有后台工作人员,这是一种非常容易使用的技术。

我怀疑如果后台任务需要了解它正在进行大量调用的形式,那么你可能会有一个值得思考的问题分离。