我无法控制事件时发生跨线程事件问题 [英] cross threading event issue when i don't control the event

查看:122
本文介绍了我无法控制事件时发生跨线程事件问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了线程问题,我不确定该怎么办.

我正在使用Microsoft POS for .NET初始化一些POS硬件.当在本地使用时,它的速度非常慢,并且在终端服务上是不可接受的.三个设备需要20到40秒以上的时间.我想在应用程序启动时对对象的初始化进行多线程处理,这很容易.问题在于,其中一个对象(即扫描程序)在扫描某些对象时会引发事件.该事件不是在主线程上调用的,因为它是在BackgroundWorker上创建的.我从来没有接到活动的电话.如果这是我的活动,可以很容易地将其编组到我需要的位置,但是问题是这是我正在使用的硬件供应商的对象.有关如何解决此问题的任何想法?

我尝试将实际的事件消息发送到工作人员的完整子目录上,但这没有帮助.基本代码如下:

private void bw_DoWork(object sender, DoWorkEventArgs e)
{ 
  _scanner.Open();
  _scanner.Claim(10000);
  _scanner.DeviceEnabled = true;
}
private void OpenScanner()
{
  if (_scanner == null)
  {
    var queryScanner = (from i in _devices.OfType<DeviceInfo>() where i.LogicalNames.Contains(txtScanner.Text) select i).FirstOrDefault();
    _scanner = (PosCommon)_posExplorer.CreateInstance(queryScanner);
    var bw = new BackgroundWorker();
    bw.DoWork += this.bw_DoWork;
    bw.RunWorkerCompleted += this.bw_RunWorkerCompleted;
    bw.WorkerReportsProgress = false;
    bw.WorkerSupportsCancellation = false;
    bw.RunWorkerAsync();
  }
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    var scan = (Scanner)_scanner;
    scan.AutoDisable = false;
    scan.DecodeData = true;
    scan.DataEventEnabled = true;
    scan.DataEvent += this._Scanner_DataEvent;
    lblGotScanner.Visible = true;
}

\


安德鲁(Andrew)和约翰(John)的更多信息,

安德鲁(Andrew),实际上从未触发过_Scanner_DataEvent.供应商对象永远不会触发该事件(或者它在某个线程上的其他位置被触发.不幸的是,我对此没有任何控制,否则很容易解决.

约翰,
不,它一次只能成为扫描仪的一个对象.它锁定了硬件,因此另一个对象无法对其执行.claim.
实际的扫描仪对象本身就是供应商的代码.要使用它,请实例化它,打开它,声明它,然后启用它.我现在将其全局实例化到应用程序,并在我在UI线程中打开/声明事件之前尝试连接事件,但是它没有帮助.就像打开后一样.在后台工作人员中声称,所有事件都存在(或者根本没有事件被触发)

我想到但没有尝试过的一件事,因为它使事情变得更加复杂了,自己做线程并保持另一个线程打开.在那里捕获事件,然后将我自己的事件编组回我的UI线程.假设事件实际上是从扫描仪引发的,我认为这会起作用.
如果我删除后台工作程序,它就可以正常工作.必需的呼叫Invoke.

您需要在_Scanner_DataEvent方法中执行此操作.

private void _Scanner_DataEvent(object sender, EventArgs e)
{
  // Check InvokeRequired on whatever control/form you like (form is best imo)
  if (this.InvokeRequired)
  {	
    this.Invoke(new DataEventDelegate(_Scanner_DataEvent), new object[] { sender, e });
  }
  else
  {
     // Your existing logic here.
  }
}



对于WPF,它就像是这样(不是100%地确定语法的含义,但这应该可以使您接近解决方案):

private void _Scanner_DataEvent(object sender, EventArgs e)
{
  // Check InvokeRequired on whatever control/form you like (form is best imo)
  if (!this.Dispatcher.CheckAccess())
  {
    this.Dispatcher.Invoke(DispatcherPriority.Normal, new DataEventDelegate(_Scanner_DataEvent), new object[] { sender, e });
  }
  else
  {
     // Your existing logic here.
  }
}



请参见MSDN 此处 [


我不确定您的设计.扫描仪是否具有多线程功能?多个扫描程序对象可以同时访问不同的设备吗?


我看不到线程间通信中应该包含哪些线程.最大的区别是:是否涉及UI线程.让我们从UI开始.

如果您需要通过非UI线程来操作UI,则无法通过任何调用来实现(不是很自然吗?).相反,您必须使用System.Threading.DispatcherSystem.Windows.Forms.Control的方法InvokeBeginInvoke(无论哪个控件实例;它应该是您的Application中包括的任何内容,例如您的Form).
Dispatcher适用于Forms和WPF.

您将找到有关其工作方式的所有详细信息,并应在我过去的解决方案中使用它:
Control.Invoke()与Control.BeginInvoke() [ ^ ],
Treeview扫描仪和MD5的问题 [用于线程通信和线程间调用的简单阻塞队列 [ ^ ].

对于v.4.0,已经存在一个类似的容器(BlockingCollection),但是您应该使用上面引用的我的文章中显示的使用模式.

—SA


I''m having a threading issue that I''m not sure what to do with.

I''m using Microsoft POS for .NET to initialize some POS hardware. It''s horribly slow when used locally and unacceptable on Terminal Services. It takes upwards of 20-40 seconds for three devices. I wanted to multi-thread the initialize of the object on app start up, which was easy. The problem is that one of the objects, the scanner, throws an event when it scans something. The event isn''t called from the main thread, since it was created on a BackgroundWorker. I never get the call to the event. If it were my event, it''d be easy to marshal it where I needed, but the problem is that it''s a hardware vendor''s object that I''m using. Any ideas on how to fix this?

I tried putting the actual wire up to the event on the complete sub of the worker but that didn''t help. Here''s the basic code:

private void bw_DoWork(object sender, DoWorkEventArgs e)
{ 
  _scanner.Open();
  _scanner.Claim(10000);
  _scanner.DeviceEnabled = true;
}
private void OpenScanner()
{
  if (_scanner == null)
  {
    var queryScanner = (from i in _devices.OfType<DeviceInfo>() where i.LogicalNames.Contains(txtScanner.Text) select i).FirstOrDefault();
    _scanner = (PosCommon)_posExplorer.CreateInstance(queryScanner);
    var bw = new BackgroundWorker();
    bw.DoWork += this.bw_DoWork;
    bw.RunWorkerCompleted += this.bw_RunWorkerCompleted;
    bw.WorkerReportsProgress = false;
    bw.WorkerSupportsCancellation = false;
    bw.RunWorkerAsync();
  }
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    var scan = (Scanner)_scanner;
    scan.AutoDisable = false;
    scan.DecodeData = true;
    scan.DataEventEnabled = true;
    scan.DataEvent += this._Scanner_DataEvent;
    lblGotScanner.Visible = true;
}

\


MORE INFO for andrew and john,

andrew, the _Scanner_DataEvent is never actually fired. the vendor object never fires off the event(or it''s being fired somewhere else on some thread. I don''t have any control over that unfortunately or this would be easy to fix.

John,
no it''s one object to the scanner at one time. It locks the hardware so another object can''t do a .claim on it.
the actual scanner object itself is the vendor''s code. To use it, you instantiate it, open it, claim it and then enable it. I instantiate it globally to the app now, and tried wiring up the event before I open/claim it in the UI thread, but it didn''t help it. It''s like once it''s opened.claimed on the background worker, all events are there (or no event is getting fired at all)

one thing i thought of but haven''t tried because it complicates it a lot more, is to do the threading myself and keep the other thread open. catch the event there, and then marshal my own event back to my UI thread. Assuming the event is actually getting raised from the scanner, i think this would work.
if i remove the background worker it works fine.

解决方案

The simplest approach to handling this problem is to perform an InvokeRequired check (assuming WinForms here) and if its required call Invoke.

You would need to perform this within your _Scanner_DataEvent method.

private void _Scanner_DataEvent(object sender, EventArgs e)
{
  // Check InvokeRequired on whatever control/form you like (form is best imo)
  if (this.InvokeRequired)
  {	
    this.Invoke(new DataEventDelegate(_Scanner_DataEvent), new object[] { sender, e });
  }
  else
  {
     // Your existing logic here.
  }
}



For WPF, it would be something like (not 100% sure on the syntax off the top of my head, but that should get you near the solution):

private void _Scanner_DataEvent(object sender, EventArgs e)
{
  // Check InvokeRequired on whatever control/form you like (form is best imo)
  if (!this.Dispatcher.CheckAccess())
  {
    this.Dispatcher.Invoke(DispatcherPriority.Normal, new DataEventDelegate(_Scanner_DataEvent), new object[] { sender, e });
  }
  else
  {
     // Your existing logic here.
  }
}



See MSDN here[^].

This will cause the event to be ''re-executed'' within the UI thread context.


I''m not sure about your design. Is the scanner multi-thread capable? Can multiple scanner objects access different devices at the same time?


I cannot see what threads should be involved in inter-thread communication. The big difference: if UI thread involved or not. Let''s start with UI.

If you need to operate UI from a non-UI thread, you cannot do it through any calls (isn''t that natural?). Instead, you have to use methods Invoke or BeginInvoke of System.Threading.Dispatcher or System.Windows.Forms.Control (no matter which control instance; it should be anything included in your Application, for example, your Form).
Dispatcher works for both Forms and WPF.

You will find all the detail on how it works and should be used in my past Solutions:
Control.Invoke() vs. Control.BeginInvoke()[^],
Problem with Treeview Scanner And MD5[^].

If you want to communicate between your custom threads, the situation is more difficult. For a functionality very similar to Invocation on the UI thread, you should use blocking queue.
You can find my implementation here complete with full source code and usage samples:
Simple Blocking Queue for Thread Communication and Inter-thread Invocation[^].

For v.4.0, a similar container already exists (BlockingCollection), but you should use usage patterns shown in my article referenced above.

—SA


这篇关于我无法控制事件时发生跨线程事件问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆