C#跨线程通信

在C#.NET中,我编写了以下简单的后台工作线程:

public class MyBackgrounder { public delegate void dlgAlert(); public dlgAlert Alert; public event EventHandler eventAlert; Thread trd; public void Start() { if (trd == null || trd.ThreadState == ThreadState.Aborted) { trd = new Thread(new ThreadStart(Do)); } trd.IsBackground = true; trd.Priority = ThreadPriority.BelowNormal; trd.Start(); } void Do() { Thread.Sleep(3000); Done(); } void Done() { if (Alert != null) Alert(); if (eventAlert != null) eventAlert(this, new EventArgs()); Kill(); } public void Kill() { if (trd != null) trd.Abort(); trd = null; } } static class Program { [STAThread] static void Main() { MyBackgrounder bg = new MyBackgrounder(); bg.eventAlert += new EventHandler(bg_eventAlert); bg.Alert = jobDone; bg.Start(); } static void bg_eventAlert(object sender, EventArgs e) { // here, current thread's id has been changed } static void jobDone() { // here, current thread's id has been changed } } 

它只等待3秒(完成它的工作),然后引发一个指定的事件或调用一个委托。 直到这里没有问题,一切正常。 但是当我看到’ Thread.CurrentThread.ManagedThreadId ‘时,我看到它是后台线程! 也许这是正常的,但我怎么能防止这种行为? 如果您测试’ System.Windows.Forms.Timer ‘组件并处理其’ Tick ‘事件,您可以看到’ Thread.CurrentThread.ManagedThreadId ‘尚未从主线程ID更改为其他任何内容。

我能做什么?

如果您使用的是Windows窗体,则可以执行以下操作:

  1. 在您的表单添加属性

     private readonly System.Threading.SynchronizationContext context; public System.Threading.SynchronizationContext Context { get{ return this.context;} } 
  2. 在“表单”构造函数中设置属性

     this.context= WindowsFormsSynchronizationContext.Current; 
  3. 使用此属性将其作为构造函数参数传递给后台工作程序。 这样您的工作人员就会了解您的GUI上下文。 在后台工作者中创建类似的属性。

     private readonly System.Threading.SynchronizationContext context; public System.Threading.SynchronizationContext Context { get{ return this.context;} } public MyWorker(SynchronizationContext context) { this.context = context; } 
  4. 更改您的Done()方法:

     void Done() { this.Context.Post(new SendOrPostCallback(DoneSynchronized), null); } void DoneSynchronized(object state) { //place here code You now have in Done method. } 
  5. 在DoneSynchronized中您应始终在您的GUI线程中。

Timer在主线程中运行(如果主线程真的很忙,将会延迟关闭)。 如果由于某种原因你想从主线程中调用jobDone(),你可以使用一个BackGroundWorker对象,该对象在内部有线程,并且有一些特定的事件,这些事件被称为主线程的线程安全(允许UI更新等)

究竟是什么让后台线程出现问题? 你所描述的,到目前为止对我来说都很好。

您检查进行比较的System.Windows.Forms.Timer可能正在UI线程上运行,这确实会使其成为非后台线程。

从Thread.IsBackground属性的MSDN文档(由我突出显示):

线程是后台线程或前台线程。 后台线程前台线程相同 ,除了后台线程不会阻止进程终止。 一旦属于进程的所有前台线程终止,公共语言运行库就结束该进程。 任何剩余的后台线程都会停止并且不会完成。

这个设计怎么样:

  1. 在表单中创建一个EventWaitHandle对象。

     private EventWaitHandle layer2changed = new EventWaitHandle(false, EventResetMode.ManualReset); 
  2. 在Timer事件处理程序中执行以下操作:

     if (this.layer2changed.WaitOne(0, false)) { // perform UI updates according to some public properties of layer1/layer2 this.layer2changed.Reset(); } 
  3. 在其构造函数中将对layer2changed的引用传递给layer1 / layer2等。

  4. 每当你的Done()方法做一些需要更新UI信号的事件时:

      this.referencedLayer2Changed.Set(); 

但请注意,layer2和UI中的更改之间存在时间差(Form.Timer间隔越长 – 差异越长)。

请注意每个人:这是一个解决方案,它出自losingsleep在评论中对其问题所指定的额外要求。 请在downvoting之前阅读它们。 🙂

我认为还有另一个解决方案,不需要在底层(layer2)中引用“System.Windows.Forms”:
layer1(UI表单)提供了layer2可以使用的超时实现。

有一个接口:

 namespace Layer2 { public delegate void TimeoutAlert(); public interface IApplicationContext { void SetTimeout(TimeoutAlert timeoutAlerthandler, int milliseconds); } } 

和Layer2中的工作类:

 namespace Layer2 { public class Worker { IApplicationContext _context; public void DoJob(IApplicationContext context) { _context = context; _context.SetTimeout(JobTimedOut,3000); // nothing ... (wait for other devices or user events) } void JobTimedOut() { // do something suitable for timeout error with _context or anything else } } } 

在UI,我们有这个:

 using Layer2; namespace Layer1 { public partial class frmTest : Form, IApplicationContext { int timeout; TimeoutAlert _timeoutAlerthandler; public frmTest() { InitializeComponent(); } private void frmTest_Load(object sender, EventArgs e) { Worker w = new Worker(); w.DoJob(this); } private void timer1_Tick(object sender, EventArgs e) { // it's the handler of 'Timer' component named 'timer1' timer1.Enabled = false; if (_timeoutAlerthandler != null) _timeoutAlerthandler(); } #region IApplicationContext Members void IApplicationContext.SetTimeout(TimeoutAlert timeoutAlerthandler, int milliseconds) { _timeoutAlerthandler = timeoutAlerthandler; timeout = milliseconds; timer1.Interval = milliseconds; timer1.Enabled = true; } #endregion } } 

我再次注意到我的情景:
我有一个Windows应用程序项目(layer1)使用核心项目(layer2)来做一些工作。 在某些方法中,核心需要非阻塞超时检查而不创建不同的线程,因此在超时后它需要再次与UI交互。 对不起,如果我不能描述更多,因为它有点复杂(对我来说!)。