【问题标题】:Can I create a thread that can modify the user interface, and that I can abort?我可以创建一个可以修改用户界面并且可以中止的线程吗?
【发布时间】:2008-11-19 14:46:32
【问题描述】:

我的表单上有一个冗长的用户界面操作,只要触发事件就会触发。我不想在操作发生时阻止 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();
}

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

【问题讨论】:

    标签: c# .net user-interface multithreading


    【解决方案1】:

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

    第二——尽量避免中止线程;这不好 - 你应该更喜欢干净关闭(例如 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;
            }
        }
    

    这仍然有点混乱,但可以说比左右中止线程更干净......

    【讨论】:

      【解决方案2】:

      您可以使用框架 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();
              }
      

      【讨论】:

      • 所以要取消线程,我可以使用抛出异常的委托函数再次调用 operation.Post() 吗?还是要等到第一个 operation.Post() 完成?
      • ...或者 PostOperationCompleted 会中止操作吗?
      • 它不会中止,它只是完成异步操作。我认为你甚至可以省略它。另一种方法是不使用 AsyncOperationManager,但据说更好:SynchronizationContext syncContext; this.syncContext = SynchronizationContext.Current; this.syncContext.Post(delegate{},null)
      • 我不想等待操作完成 - 我想在收到另一个事件后立即终止它。
      • 看不清楚。 bw.CancelAsync() 是我看到的唯一选项。另一种选择是使用 AOP。看这个项目:secure.codeproject.com/KB/cs/AOPInvokeRequired.aspx
      【解决方案3】:

      我在这里回答你是因为 cmets 太短了。 我个人使用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,它将是异步的,如果你使用,Send 它将是同步的。

      取消: bw.CancelAsync();

      【讨论】:

      • 这很好,但是如果我更新任何记录的控件,我仍然需要调用 Invoke()。我真的在寻找可以包装所有需要调用的调用的东西——也许我需要查看动态方法。
      【解决方案4】:

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

      我真的无法判断这是否有用,无论如何,这是一种无需后台工作人员即可轻松使用的技术。

      我怀疑,如果后台任务需要非常了解它正在执行大量调用的表单,那么您可能会遇到一个值得考虑的关注点分离问题。

      【讨论】:

      • OnProgress 回调对我没有用 - 我仍然需要编写 100 个不同的函数,每个函数对应 100 行代码。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多