C# 调用跨线程事件的最简洁方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22356/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Cleanest Way to Invoke Cross-Thread Events
提问by Nick
I find that the .NET event model is such that I'll often be raising an event on one thread and listening for it on another thread. I was wondering what the cleanest way to marshal an event from a background thread onto my UI thread is.
我发现 .NET 事件模型是这样的,我经常在一个线程上引发事件并在另一个线程上侦听它。我想知道将事件从后台线程编组到我的 UI 线程的最简洁方法是什么。
Based on the community suggestions, I've used this:
根据社区的建议,我使用了这个:
// earlier in the code
mCoolObject.CoolEvent+=
new CoolObjectEventHandler(mCoolObject_CoolEvent);
// then
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
if (InvokeRequired)
{
CoolObjectEventHandler cb =
new CoolObjectEventHandler(
mCoolObject_CoolEvent);
Invoke(cb, new object[] { sender, args });
return;
}
// do the dirty work of my method here
}
采纳答案by Shaun Austin
A couple of observations:
几个观察:
- Don't create simple delegates explicitly in code like that unless you're pre-2.0 so you could use:
- 不要在这样的代码中显式创建简单的委托,除非您是 2.0 之前的版本,否则您可以使用:
BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent),
sender,
args);
Also you don't need to create and populate the object array because the args parameter is a "params" type so you can just pass in the list.
I would probably favor
Invoke
overBeginInvoke
as the latter will result in the code being called asynchronously which may or may not be what you're after but would make handling subsequent exceptions difficult to propagate without a call toEndInvoke
. What would happen is that your app will end up getting aTargetInvocationException
instead.
此外,您不需要创建和填充对象数组,因为 args 参数是“params”类型,因此您只需传入列表即可。
我可能更喜欢后者
Invoke
,BeginInvoke
因为后者会导致代码被异步调用,这可能是也可能不是您所追求的,但是如果不调用EndInvoke
. 会发生的情况是,您的应用最终会得到一个TargetInvocationException
替代。
回答by Konrad Rudolph
I shun redundant delegate declarations.
我避免多余的委托声明。
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
if (InvokeRequired)
{
Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);
return;
}
// do the dirty work of my method here
}
For non-events, you can use the System.Windows.Forms.MethodInvoker
delegate or System.Action
.
对于非事件,您可以使用System.Windows.Forms.MethodInvoker
委托或System.Action
.
EDIT: Additionally, every event has a corresponding EventHandler
delegate so there's no need at all to redeclare one.
编辑:此外,每个事件都有一个相应的EventHandler
委托,因此根本不需要重新声明一个。
回答by Konrad Rudolph
I've always wondered how costly it is to alwaysassume that invoke is required...
我一直想知道总是假设需要调用是多么昂贵......
private void OnCoolEvent(CoolObjectEventArgs e)
{
BeginInvoke((o,e) => /*do work here*/,this, e);
}
回答by On Freund
You can try to develop some sort of a generic component that accepts a SynchronizationContextas input and uses it to invoke the events.
您可以尝试开发某种通用组件,它接受SynchronizationContext作为输入并使用它来调用事件。
回答by gbc
As an interesting side note, WPF's binding handles marshaling automatically so you can bind the UI to object properties that are modified on background threads without having to do anything special. This has proven to be a great timesaver for me.
作为一个有趣的旁注,WPF 的绑定会自动处理封送处理,因此您可以将 UI 绑定到在后台线程上修改的对象属性,而无需执行任何特殊操作。事实证明,这对我来说是一个很好的节省时间。
In XAML:
在 XAML 中:
<TextBox Text="{Binding Path=Name}"/>
回答by Domenic
I have some code for thisonline. It's much nicer than the other suggestions; definitely check it out.
我在网上有一些代码。它比其他建议好得多;一定要检查一下。
Sample usage:
示例用法:
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
// You could use "() =>" in place of "delegate"; it's a style choice.
this.Invoke(delegate
{
// Do the dirty work of my method here.
});
}
回答by Dmitri Nesteruk
I think the cleanest way is definitelyto go the AOP route. Make a few aspects, add the necessary attributes, and you never have to check thread affinity again.
我认为最干净的方法肯定是走 AOP 路线。创建几个方面,添加必要的属性,您就不必再次检查线程关联性。
回答by TarPista
I made the following 'universal' cross thread call class for my own purpose, but I think it's worth to share it:
我出于自己的目的制作了以下“通用”跨线程调用类,但我认为值得分享:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace CrossThreadCalls
{
public static class clsCrossThreadCalls
{
private delegate void SetAnyPropertyCallBack(Control c, string Property, object Value);
public static void SetAnyProperty(Control c, string Property, object Value)
{
if (c.GetType().GetProperty(Property) != null)
{
//The given property exists
if (c.InvokeRequired)
{
SetAnyPropertyCallBack d = new SetAnyPropertyCallBack(SetAnyProperty);
c.BeginInvoke(d, c, Property, Value);
}
else
{
c.GetType().GetProperty(Property).SetValue(c, Value, null);
}
}
}
private delegate void SetTextPropertyCallBack(Control c, string Value);
public static void SetTextProperty(Control c, string Value)
{
if (c.InvokeRequired)
{
SetTextPropertyCallBack d = new SetTextPropertyCallBack(SetTextProperty);
c.BeginInvoke(d, c, Value);
}
else
{
c.Text = Value;
}
}
}
And you can simply use SetAnyProperty() from another thread:
你可以简单地从另一个线程使用 SetAnyProperty() :
CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString());
In this example the above KvaserCanReader class runs its own thread and makes a call to set the text property of the lb_Speed label on the main form.
在这个例子中,上面的 KvaserCanReader 类运行它自己的线程并调用以设置主窗体上 lb_Speed 标签的文本属性。
回答by The Lonely Coder
Use the synchronisation context if you want to send a result to the UI thread. I needed to change the thread priority so I changed from using thread pool threads (commented out code) and created a new thread of my own. I was still able to use the synchronisation context to return whether the database cancel succeeded or not.
如果要将结果发送到 UI 线程,请使用同步上下文。我需要更改线程优先级,因此我不再使用线程池线程(注释掉代码)并创建了一个我自己的新线程。我仍然能够使用同步上下文来返回数据库取消是否成功。
#region SyncContextCancel
private SynchronizationContext _syncContextCancel;
/// <summary>
/// Gets the synchronization context used for UI-related operations.
/// </summary>
/// <value>The synchronization context.</value>
protected SynchronizationContext SyncContextCancel
{
get { return _syncContextCancel; }
}
#endregion //SyncContextCancel
public void CancelCurrentDbCommand()
{
_syncContextCancel = SynchronizationContext.Current;
//ThreadPool.QueueUserWorkItem(CancelWork, null);
Thread worker = new Thread(new ThreadStart(CancelWork));
worker.Priority = ThreadPriority.Highest;
worker.Start();
}
SQLiteConnection _connection;
private void CancelWork()//object state
{
bool success = false;
try
{
if (_connection != null)
{
log.Debug("call cancel");
_connection.Cancel();
log.Debug("cancel complete");
_connection.Close();
log.Debug("close complete");
success = true;
log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());
}
}
catch (Exception ex)
{
log.Error(ex.Message, ex);
}
SyncContextCancel.Send(CancelCompleted, new object[] { success });
}
public void CancelCompleted(object state)
{
object[] args = (object[])state;
bool success = (bool)args[0];
if (success)
{
log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());
}
}