如何在多个线程之间进行通信?

时间:2010-01-26 03:28:43

标签: c# winforms multithreading thread-safety

我正在为另一个程序编写一个插件,该程序使用本机程序打开一系列文件以从中提取一些数据。我遇到的一个问题是该过程需要很长时间,我想保持用户界面不挂。另外,我还想让用户在完成之前取消该过程。在过去,我使用后台工作者来处理这类事情,但在这种情况下,我认为BackgroundWorker不会起作用。

要通过我使用的API创建插件,可以通过继承IAPICommand接口来创建自定义命令。该接口包括Execute(应用程序app)方法。然后实例化该类,当用户在程序中唤起自定义命令时,程序将调用Execute()方法。

Execute()方法在调用时传递对当前Application对象的引用,并且该应用程序对象用于打开文件以从中提取数据。但是,当原始Execute()线程以外的线程请求时,应用程序实例无法打开文档。

因此,通常UI将存在于主线程上,并且将在辅助线程上执行耗时的数据提取。但是,在这种情况下,必须在主线程上执行数据提取,并且我需要为UI创建辅助线程。

这是代码的精简版本。

class MyCommand:IAPICommand
{
    public void Execute(Application app) // method from IAPICommand
    {
        Thread threadTwo= new Thread(ShowFormMethod);
        threadTwo.Start();
    }

    public void ProcessWidget(Widget w, Application app)
    { 
        //uses an App to work some magic on C
        //app must be called from the original thread that called ExecuteCommand()
    }

    //method to open custom form on a seperatethread
    public void ShowFormMethod()
    {
      MyForm form = new MyForm();
      form.ShowDialog();  
    }
}

这是一个流程图,显示了我认为这应该最终如何运作。

alt text http://dl.dropbox.com/u/113068/SOMLibThreadingDiagram.jpg

  1. 这个图表是否有意义,如果是这样,我甚至采取正确的方法来解决这个问题?
  2. 一旦主线程启动UI线程,我希望它等待用户选择要处理的小部件,或者通过关闭表单结束命令(图中的红色数字)。如何使主线程等待,以及如何触发它继续处理或在UI线程结束时继续结束?我以为我可以让主线程在Monitor锁上等待。然后,UI线程将填充要处理的静态Widgets列表,然后脉冲主​​线程以触发处理。当窗体关闭时,UI线程也会激活主线程,如果要处理的窗口小部件列表为空,主线程将知道继续到命令的结尾。
  3. 如何让主线程将窗口小部件处理的进度或完成传回UI线程(图中的黄色箭头)?我刚刚使用Form的BeginInvoke()方法来执行此操作吗?
  4. 如何允许UI线程取消窗口小部件处理(图中的绿色箭头)?我想我可以设置一个静态布尔标志,在处理每个小部件之前检查它?

4 个答案:

答案 0 :(得分:14)

在您的应用程序中创建多个创建表单的线程通常是一个坏主意。使这项工作并非不可能,但它比你想象的要困难得多,因为父子关系中的表单会相互发送消息,而当它们发送消息时,发送消息的表单会阻塞接收处理它。

将此内容与您明确执行的线程之间的消息传递或同步相结合,并且很容易导致死锁。因此,一般情况下,最好确保为用户界面保留主线程,并在没有UI的其他线程中进行所有处理。

如果符合该设计,则后台线程可以使用Control.BeginInvoke将消息传递给UI线程,而无需等待消息处理。

答案 1 :(得分:2)

除了其他答案之外,我建议您使用ProcessWidget中的回调方法将进度传递回调用线程。要过早地停止工作线程,如果它经常更新调用者,您可以使用回调将停止信号返回到工作线程。或者使用单独的回调方法定期检查go / no-go。或者设置一个(gasp!)全局静态标志,工作者定期检查。或者在工作线程上调用Thread.Abort并让它捕获ThreadAbortException以清理任何资源。

答案 2 :(得分:1)

我假设主机应用程序是WinForms应用程序。

您需要在Execute方法中保存原始线程中的SynchronizationContext,然后调用其Send方法在主机的UI线程上执行代码。

例如:

class MyCommand:IAPICommand
{
    SynchronzationContext hostContext;
    public void Execute(Application app) // method from IAPICommand
    {
        hostContext = SynchronzationContext.Current;
        Thread threadTwo = new Thread(ShowFormMethod);
        threadTwo.Start();
    }

    public void ProcessWidget(Widget w, Application app)
    { 
        //uses an App to work some magic on C
        //app must be called from the original thread that called ExecuteCommand()
        SomeType someData = null;
        hostContext.Send(delegate { someData = app.SomeMethod(); }, null);
    }
}

答案 3 :(得分:0)

如果你看一下Java swing,它就是一个很好的例子:

1)主线程负责处理所有UI请求。这将从应用程序中删除任何竞争条件。

2)任何时候任何“工作”都要完成,产生一个线程(或线程池)并完成工作。因此,除了几微秒​​之外,主线程不会被阻止,并且无论发生什么事情,UI都会完全响应。

3)在所有语言中都必须有线程中断机制。在java中,您在线程上调用.interrupt(),当前正在运行的线程在执行任何地方时都会抛出InterruptedException。你的工作是抓住那个异常,弄清楚你是否真的被打断了(读这个部分的javadocs),如果你只是让自己死掉(退出run方法)。

1 + 2 =不引人注目的客户互动

3 =杀死线程

3的替代(如果3太复杂)是给线程一个方法.kill();该方法设置一个kill标志。当您从循环中的硬盘驱动器中读取缓冲区时,请检查是否设置了kill标志,是否已经脱离循环,关闭处理程序,并返回run方法。

编辑:抱歉忘了提及进度报告:

您的主题应该有一个公开的线程安全方法来获取“进度报告”,或者更确切地说是一个包含有关进度信息的数据结构。您的UI线程应定期(比如说每0.5秒)检查线程的进度报告并更新UI的进度条。通过UI线程检查,我的意思是显示进度的窗口小部件请求使用计时器的最新信息重新呈现,直到完成。