C# 如何在多个线程之间通信?

C# 如何在多个线程之间通信?,c#,winforms,multithreading,thread-safety,C#,Winforms,Multithreading,Thread Safety,我正在为另一个程序编写一个插件,它使用本机程序打开一系列文件,从中提取一些数据。我遇到的一个问题是,这个过程需要很长时间,我希望用户界面不会挂起。另外,我还想让用户能够在流程完成之前取消流程。在过去,我使用了一个后台工作人员来完成这类工作,但在这种情况下,我认为后台工作人员不起作用 要通过我使用的API创建插件,可以通过从IAPICommand接口继承来创建自定义命令。此接口包括一个执行(应用程序应用程序)方法。然后,当用户在程序中调用自定义命令时,该类被实例化,程序调用Execute()方法

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

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

调用Execute()方法时,会向其传递对当前应用程序对象的引用,正是该应用程序对象用于打开要从中提取数据的文件。但是,当原始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();  
    }
}
下面是一个流程图,显示了我认为这最终应该如何工作

  • 这个图表有意义吗?如果有,我是否采取了正确的方法来解决这个问题
  • 一旦主线程启动UI线程,我希望它等待用户选择要处理的小部件,或者通过关闭表单(图上的红色数字)来结束命令。如何使主线程等待,以及如何触发它继续处理或在UI线程结束时继续到结束?我在想我可以让主线程在监视器锁上等待。UI线程随后将填充要处理的小部件的静态列表,然后脉冲主线程以触发处理。当窗体关闭时,UI线程也会触发主线程,如果要处理的窗口小部件列表为空时触发主线程,则主线程会知道继续执行到命令末尾
  • 如何允许主线程将小部件处理的进度或完成情况反馈给UI线程(图中的黄色箭头)?我是否只是使用表单的BeginInvoke()方法来实现这一点
  • 如何允许UI线程取消小部件处理(图中的绿色箭头)?我想我可以设置一个静态布尔标志,在处理每个小部件之前进行检查

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

    您需要在
    Execute
    方法中保存来自原始线程的,然后调用其
    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);
        }
    }
    

    在应用程序中有多个线程,每个线程都创建表单,这通常不是一个好主意。要做到这一点并非不可能,但这比你想象的要困难得多,因为处于父子关系中的表单会相互发送消息,当它们发送消息时,发送消息的表单会阻塞,直到接收消息的表单处理完为止

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


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

    除了其他答案之外,我建议您使用ProcessWidget的回调方法将进度传递回调用线程。要提前停止工作线程,可以使用回调向工作线程返回暂停信号(如果它足够频繁地更新调用方)。或者使用单独的回调方法定期检查是否通过。或者设置一个(喘息!)全局静态标志,让工作人员定期检查。或者在工作线程上调用Thread.Abort,并让它捕获ThreadAbortException以清理任何资源

    如果你看一下Java swing,它是一个很好的例子,说明了如何做到这一点:

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

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

    3) 在所有语言中都必须有线程中断机制。在java中,您在线程上调用.interrupt(),当前运行的线程在执行的任何地方都会抛出InterruptedException。您的任务是捕获该异常,找出是否真的被中断(请阅读本部分的javadocs),以及是否让自己死掉(返回run方法)

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

    3=终止线程

    3的替代方法(如果3太复杂)是给线程一个方法.kill();该方法设置一个kill标志。在循环中从硬盘读取缓冲区时,检查是否设置了kill标志,如果设置了kill标志,则断开循环,关闭处理程序,然后返回run方法

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

    您的线程应该有一个公开的线程安全方法来获取“进度报告”,或者更确切地说是一个包含进度信息的数据结构。您的UI线程应该定期(比如每0.5秒)检查线程的进度报告并更新UI的