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

C# 我可以创建一个线程来修改用户界面,并且可以中止吗?,c#,.net,user-interface,multithreading,C#,.net,User Interface,Multithreading,我的表单上有一个冗长的用户界面操作,每当触发事件时都会触发该操作。我希望在另一个线程中执行该操作,并在事件再次触发时中止该线程并重新启动,而不是在操作发生时让UI块 但是,为了安全地更改窗体上的控件,我需要使用窗体的Invoke或BeginInvoke方法。如果我这样做,那么我可以将所有UI操作放在一个函数中,如下所示: private delegate void DoUIStuffDelegate(Thing1 arg1, Thing2 arg2); private void doUIStuf

我的表单上有一个冗长的用户界面操作,每当触发事件时都会触发该操作。我希望在另一个线程中执行该操作,并在事件再次触发时中止该线程并重新启动,而不是在操作发生时让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();
}
但是如果我这样做,那么我就失去了在单独线程中工作的好处。或者,我可以将它们放在各自的函数中,如下所示:

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();
}
但这是一个可怕的,无法维护的混乱。有没有一种方法可以创建一个线程来修改用户界面,并且我可以中止它?所以我可以这样做:

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();
}

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

首先-不要禁用跨线程检查。。。窗体具有线程关联

第二,尽量避免中止线程;这不好-您应该更喜欢干净的关机(例如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;
        }
    }
void worker\u DoWork(对象发送方,DoWorkEventArgs e)
{
试一试{
动作更新=thingToDo=>
{
如果(worker.CancellationPending)抛出新的SomeException();
这个。调用(thingToDo);
};
//...
字符串tmp=“abc”;//长时间运行
更新(()=>this.Text=tmp);
tmp=“def”;//长时间运行
更新(()=>textbox1.Text=tmp);
}捕获(某些例外){
e、 取消=真;
}
}

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

您可以使用framework 2中引入的对象同步上下文避免使用Invoke。好的,这是一样的,你用一种东西代替另一种,但事实上,它更有效,更健壮。 无论如何,交叉线程需要进行检查,因为如果没有此检查,您永远无法访问在另一个线程中创建的控件

请阅读以下内容:

一些代码来展示我的想法:

   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();
        }

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

取消:
CancelAsync()

除了BackgroundWorker建议和相关注释之外,BackgroundWorker会将OnProgress回调封送回UI线程,这样您就可以从那里更新内容,而无需执行Invoke()

我真的不知道这是否有帮助,而且不管怎样,这是一种不用背景工人就可以轻松使用的技术


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

因此,要取消线程,我是否可以使用引发异常的委托函数再次调用operation.Post()?或者这会等到第一个操作.Post()完成吗?…或者PostOperationCompleted会中止该操作吗?它不会中止,它只完成异步操作。我认为您甚至可以省略它。另一种方法是不使用AsyncOperationManager,但据说它更好:SynchronizationContext syncContext;this.syncContext=SynchronizationContext.Current;this.syncContext.Post(委托{},null)我不想等待操作完成-我想在收到另一个事件后立即终止它。我看不清楚它。CancelAsync()是我看到的唯一选项。另一种选择是使用AOP。看看这个项目:这很好,但是如果我更新任何记录的控件,我仍然需要调用Invoke()。我真的在寻找能够包装所有需要调用的调用的东西——也许我需要看看动态方法。OnProgress回调对我没有用处——我仍然需要编写100个不同的函数,100行代码中的每一行对应一个。