C# 如何使事件处理程序异步运行?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1916095/
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
How do I make an eventhandler run asynchronously?
提问by PICyourBrain
I am writing a Visual C# program that executes a continuous loop of operations on a secondary thread. Occasionally when that thread finishes a task I want it to trigger an eventhandler. My program does that but the when the event handler is triggered, the secondary thread waits until the event handler is finished before continuing the thread. How do I make it continue? Here is the way I currently have it structured...
我正在编写一个 Visual C# 程序,它在辅助线程上执行连续的操作循环。有时,当该线程完成一项任务时,我希望它触发一个事件处理程序。我的程序会这样做,但是当事件处理程序被触发时,辅助线程在继续线程之前等待事件处理程序完成。我如何让它继续?这是我目前的结构方式......
class TestClass
{
private Thread SecondaryThread;
public event EventHandler OperationFinished;
public void StartMethod()
{
...
SecondaryThread.Start(); //start the secondary thread
}
private void SecondaryThreadMethod()
{
...
OperationFinished(null, new EventArgs());
... //This is where the program waits for whatever operations take
//place when OperationFinished is triggered.
}
}
This code is part of an API for one of my devices. When the OperationFinished event is triggered I want the client application to be able to do whatever it needs to (i.e. update the GUI accordingly) without haulting the API operation.
此代码是我的其中一台设备的 API 的一部分。当 OperationFinished 事件被触发时,我希望客户端应用程序能够执行它需要的任何操作(即相应地更新 GUI)而无需拖拽 API 操作。
Also, if I do not want to pass any parameters to the event handler is my syntax correct by using OperationFinished(null, new EventArgs())
?
另外,如果我不想将任何参数传递给事件处理程序,使用OperationFinished(null, new EventArgs())
?
采纳答案by STW
So you want to raise the event in a manner that prevents the listeners from blocking the background thread? Gimme a couple minutes to whip up an example; it's pretty simple :-)
所以你想以一种防止监听器阻塞后台线程的方式引发事件?给我几分钟时间来举个例子;这很简单:-)
Here we go: first an important note!Whenever you call BeginInvoke
you must call the corresponding EndInvoke
, otherwise if the invoked method threw an exception orreturned a value then the ThreadPool thread will never be released back to the pool, resulting in a thread-leak!
我们开始: 首先是一个重要的注意事项!无论何时调用BeginInvoke
都必须调用相应的EndInvoke
,否则如果调用的方法抛出异常或返回值,则永远不会将 ThreadPool 线程释放回池中,从而导致线程泄漏!
class TestHarness
{
static void Main(string[] args)
{
var raiser = new SomeClass();
// Emulate some event listeners
raiser.SomeEvent += (sender, e) => { Console.WriteLine(" Received event"); };
raiser.SomeEvent += (sender, e) =>
{
// Bad listener!
Console.WriteLine(" Blocking event");
System.Threading.Thread.Sleep(5000);
Console.WriteLine(" Finished blocking event");
};
// Listener who throws an exception
raiser.SomeEvent += (sender, e) =>
{
Console.WriteLine(" Received event, time to die!");
throw new Exception();
};
// Raise the event, see the effects
raiser.DoSomething();
Console.ReadLine();
}
}
class SomeClass
{
public event EventHandler SomeEvent;
public void DoSomething()
{
OnSomeEvent();
}
private void OnSomeEvent()
{
if (SomeEvent != null)
{
var eventListeners = SomeEvent.GetInvocationList();
Console.WriteLine("Raising Event");
for (int index = 0; index < eventListeners.Count(); index++)
{
var methodToInvoke = (EventHandler)eventListeners[index];
methodToInvoke.BeginInvoke(this, EventArgs.Empty, EndAsyncEvent, null);
}
Console.WriteLine("Done Raising Event");
}
}
private void EndAsyncEvent(IAsyncResult iar)
{
var ar = (System.Runtime.Remoting.Messaging.AsyncResult)iar;
var invokedMethod = (EventHandler)ar.AsyncDelegate;
try
{
invokedMethod.EndInvoke(iar);
}
catch
{
// Handle any exceptions that were thrown by the invoked method
Console.WriteLine("An event listener went kaboom!");
}
}
}
回答by Mark Ewer
Look at the BackgroundWorkerclass. I think it does exactly what you are asking for.
查看BackgroundWorker类。我认为它完全符合您的要求。
EDIT: What I think you are asking is how to fire an event when only a small part of the overall background task is complete. BackgroundWorker provides an event called "ProgressChanged" that allows you to report back to the main thread that some portion of the overall process is complete. Then, when all of the async work is complete, it raises the "RunWorkerCompleted" event.
编辑:我认为您要问的是如何在仅完成整个后台任务的一小部分时触发事件。BackgroundWorker 提供了一个名为“ProgressChanged”的事件,它允许您向主线程报告整个过程的某些部分已完成。然后,当所有异步工作完成时,它会引发“RunWorkerCompleted”事件。
回答by Reed Copsey
Also, if I do not want to pass any parameters to the event handler is my syntax correct by using OperationFinished(null, new EventArgs()) ?
另外,如果我不想将任何参数传递给事件处理程序,使用 OperationFinished(null, new EventArgs()) 的语法是否正确?
No. Typically, you would call it as:
不。通常,您将其称为:
OperationFinished(this, EventArgs.Empty);
You should always pass an object as a sender - it's expected in the pattern (although typically ignored). EventArgs.Empty is better than new EventArgs(), as well.
您应该始终将对象作为发件人传递 - 它在模式中是预期的(尽管通常会被忽略)。EventArgs.Empty 也比 new EventArgs() 好。
In order to fire this in a separate thread, the easiest option is probably to just use the thread pool:
为了在单独的线程中触发它,最简单的选择可能是只使用线程池:
private void RaiseOperationFinished()
{
ThreadPool.QueueUserWorkItem( new WaitCallback( (s) =>
{
if (this.OperationFinished != null)
this.OperationFinished(this, EventArgs.Empty);
}));
}
That being said, raising an event on a separate thread is something that should be thoroughly documented, as it will potentially cause unexpected behavior.
话虽如此,在单独的线程上引发事件是应该彻底记录的事情,因为它可能会导致意外行为。
回答by thecoop
Try the BeginInvoke and EndInvoke methods on the event delegate - these return immediately, and allow you to use polling, a wait handle or a callback function to notify you when the method has completed. See herefor an overview; in your example, the event is the delegate you'll be using
尝试事件委托上的 BeginInvoke 和 EndInvoke 方法 - 这些方法会立即返回,并允许您使用轮询、等待句柄或回调函数在方法完成时通知您。请参阅此处了解概述;在您的示例中,事件是您将使用的委托
回答by Ed Power
I prefer to define a method that I pass to the child thread as a delegate which updates the UI. First define a delegate:
我更喜欢定义一个方法,作为更新 UI 的委托传递给子线程。首先定义一个委托:
public delegate void ChildCallBackDelegate();
In the child thread define a delegate member:
在子线程中定义一个委托成员:
public ChildCallbackDelegate ChildCallback {get; set;}
In the calling class define the method that updates the UI. You'll need to wrap it in the target control's dispatcher since its being called from a separate thread. Note the BeginInvoke. In this context EndInvoke isn't required:
在调用类中定义更新 UI 的方法。您需要将它包装在目标控件的调度程序中,因为它是从单独的线程调用的。注意BeginInvoke。在这种情况下,不需要 EndInvoke:
private void ChildThreadUpdater()
{
yourControl.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background
, new System.Threading.ThreadStart(delegate
{
// update your control here
}
));
}
Before you launch your child thread, set its ChildCallBack property:
在启动子线程之前,设置其 ChildCallBack 属性:
theChild.ChildCallBack = new ChildCallbackDelegate(ChildThreadUpdater);
Then when the child thread wants to update the parent:
然后当子线程想要更新父线程时:
ChildCallBack();
回答by WhiteKnight
With the Task Parallel Libraryit is now possible to do the following:
使用任务并行库,现在可以执行以下操作:
Task.Factory.FromAsync( ( asyncCallback, @object ) => this.OperationFinished.BeginInvoke( this, EventArgs.Empty, asyncCallback, @object ), this.OperationFinished.EndInvoke, null );
回答by Jiefeng Koh
Maybe Method2 or Method3 below can help :)
也许下面的方法2或方法3可以帮助:)
public partial class Form1 : Form
{
private Thread SecondaryThread;
public Form1()
{
InitializeComponent();
OperationFinished += callback1;
OperationFinished += callback2;
OperationFinished += callback3;
}
private void Form1_Load(object sender, EventArgs e)
{
SecondaryThread = new Thread(new ThreadStart(SecondaryThreadMethod));
SecondaryThread.Start();
}
private void SecondaryThreadMethod()
{
Stopwatch sw = new Stopwatch();
sw.Restart();
OnOperationFinished(new MessageEventArg("test1"));
OnOperationFinished(new MessageEventArg("test2"));
OnOperationFinished(new MessageEventArg("test3"));
//This is where the program waits for whatever operations take
//place when OperationFinished is triggered.
sw.Stop();
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += "Time taken (ms): " + sw.ElapsedMilliseconds + "\n";
});
}
void callback1(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "\n";
});
}
void callback2(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "\n";
});
}
void callback3(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "\n";
});
}
public event EventHandler<MessageEventArg> OperationFinished;
protected void OnOperationFinished(MessageEventArg e)
{
//##### Method1 - Event raised on the same thread #####
//EventHandler<MessageEventArg> handler = OperationFinished;
//if (handler != null)
//{
// handler(this, e);
//}
//##### Method2 - Event raised on (the same) separate thread for all listener #####
//EventHandler<MessageEventArg> handler = OperationFinished;
//if (handler != null)
//{
// Task.Factory.StartNew(() => handler(this, e));
//}
//##### Method3 - Event raised on different threads for each listener #####
if (OperationFinished != null)
{
foreach (EventHandler<MessageEventArg> handler in OperationFinished.GetInvocationList())
{
Task.Factory.FromAsync((asyncCallback, @object) => handler.BeginInvoke(this, e, asyncCallback, @object), handler.EndInvoke, null);
}
}
}
}
public class MessageEventArg : EventArgs
{
public string Message { get; set; }
public MessageEventArg(string message)
{
this.Message = message;
}
}
}
}