2009-11-15 32 views
17

我对多线程有点熟悉,因为我已经阅读过它,但从未在实践中使用它。C#多线程 - 无控制调用

我有一个项目使用第三方库,通过引发事件来共享输入设备的状态。问题是,写入库的方式是从不同的线程引发这些事件。

我的应用程序不需要是多线程的,我遇到了很多经典的线程问题(UI控件抱怨与不同的线程进行交互,因为一段代码被迭代而被修改的集合在它上面等)。

我只想让第三方库的事件回馈给我的UI线程。具体而言,我认为应该发生的事情是:

我的类接收事件并且处理程序正在与UI不同的线程上运行。我想检测这种情况(如使用InvokeRequired),然后执行相当于BeginInvoke的控制权返回给UI线程。然后可以在类层次结构上发送适当的通知,并且只有一个线程才能触及我的所有数据。

问题是,接收这些输入事件的类不是从Control派生的,因此没有InvokeRequired或BeginInvoke。原因是我试图干净地分离UI和底层逻辑。该类仍然在UI线程上运行,它只是在类本身内没有任何UI。

现在我通过破坏这个分离来解决这个问题。我传递了一个控件的引用,该控件将显示来自我的课程的数据,并使用其调用方法。这似乎违背了分离它们的全部目的,因为现在底层类直接依赖于我的特定UI类。

也许有一种方法可以保存对运行构造函数的线程的引用,然后在执行调用命令的线程命名空间中有一些东西?

有没有办法解决这个问题?我的方法完全错误吗?

回答

1

你不需要特定的控件,任何控件(包括表单)都可以。所以你可以将它从UI中抽象出来。

2

处理方法可以简单地将数据存储到类的成员变量中。跨线程唯一的问题发生在想要将线程更新为未在该线程上下文中创建的控件时。因此,您的泛型类可以侦听事件,然后使用委托函数调用要更新的实际控件。

再一次,只有您想要更新的UI控件需要被调用以使它们线程安全。前一段时间,我写了一篇关于“Simple Solution to Illegal Cross-thread Calls in C#”的博客文章

这篇文章更详细,但非常简单(但有限)方法的关键是在您想要的UI控件上使用匿名委托函数更新:

if (label1.InvokeRequired) { 
    label1.Invoke(
    new ThreadStart(delegate { 
     label1.Text = "some text changed from some thread"; 
    })); 
} else { 
    label1.Text = "some text changed from the form's thread"; 
} 

我希望这有助于。 InvokeRequired在技术上是可选的,但是调用控件的代价很大,所以如果不需要,检查确保它不会通过调用更新label1.Text。

+0

我的问题是沿的线条更:工作线程从库通知我的应用程序,我想立即采取行动。问题是这个“立即行动”将在工作者线程上运行,完全在UI线程上发生其他事情(例如与UI无关的事物 - 例如枚举对象列表)。 – colithium

0

有没有办法解决?

是的,解决方法是为您创建一个线程安全队列。

  • 事件处理程序是由第三方线程调用
  • 事件处理程序排入东西(事件数据)到一个集合(如表),你自己
  • 事件处理程序没有什么信号你自己的thead,那里有数据在它的集合出队和处理:
    • 你的线程可能正在等待的东西(互斥或任何);当它的互斥信号由事件处理程序发出信号时,它会唤醒并检查队列。
    • 或者,也可以不定期唤醒(例如每秒一次或其他),并轮询队列。

在这两种情况下,由于您的队列正由两个不同的线程(在第三方线程入队,和你的线程出列)写的,它需要一个线程安全的,受保护的队列。

+0

我看到很多这方面的例子。我可能是错的,但是当出队是主要的UI线程不能进入睡眠状态并且不能坐在轮询循环中的队列时,这种技术不会工作。我没有提到这一点,但另一个线程每秒钟会引发很多次事件。 – colithium

+0

你说得对:如果你想要调用的“你的”线程不是UI线程,这只会是适当的。 – ChrisW

13

使用SynchronizationContext.Current,这将指向东西,你可以用同步。

这将做正确的事™根据应用的类型。对于WinForms应用程序,它将在主UI线程上运行。

具体来说,使用SynchronizationContext.Send方法,像这样:

SynchronizationContext context = 
    SynchronizationContext.Current ?? new SynchronizationContext(); 

context.Send(s => 
    { 
     // your code here 
    }, null); 
+7

+1。请注意,如果程序是控制台应用程序,则'SynchronizationContext.Current'为空。一个很好的模式是使用'AsyncOperation'来捕获创建时的当前'SynchronizationContext'并且*做正确的事情++ *™。 http://msdn.microsoft.com/en-us/library/system.componentmodel.asyncoperation.aspx – dtb

+0

不知道那个班,谢谢你让我知道。 –

+0

它是WinForms,所以它会工作,但我的意图是独立于用户界面。控制台应用程序正在推动它的UI我会考虑,但很好知道它不会工作。 Still +1 – colithium

1

如果您正在使用WPF:

您需要到管理UI线程分派对象的引用。然后,您可以使用调度程序对象上的Invoke或BeginInvoke方法来调度发生在UI线程中的操作。

让调度员最简单的方法是使用Application.Current.Dispatcher。这是负责主要(也可能是唯一的)UI线程的调度员。

全部放在一起:

class MyClass 
{ 
    // Can be called on any thread 
    public ReceiveLibraryEvent(RoutedEventArgs e) 
    { 
     if (Application.Current.CheckAccess()) 
     { 
      this.ReceiveLibraryEventInternal(e); 
     } 
     else 
     { 
      Application.Current.Dispatcher.Invoke(
       new Action<RoutedEventArgs>(this.ReceiveLibraryEventInternal)); 
     } 
    } 

    // Must be called on the UI thread 
    private ReceiveLibraryEventInternal(RoutedEventArgs e) 
    { 
     // Handle event 
    } 
} 
+0

这是关于WPF,我看不到问题在哪里表示使用。 –