C# 阻塞和等待事件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/492020/
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
Blocking and waiting for an event
提问by Rasmus Faber
It sometimes want to block my thread while waiting for a event to occur.
它有时想在等待事件发生时阻塞我的线程。
I usually do it something like this:
我通常这样做:
private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
private void OnEvent(object sender, EventArgs e){
_autoResetEvent.Set();
}
// ...
button.Click += OnEvent;
try{
_autoResetEvent.WaitOne();
}
finally{
button.Click -= OnEvent;
}
However, it seems that this should be something that I could extract to a common class (or perhaps even something that already exists in the framework).
然而,这似乎应该是我可以提取到一个公共类的东西(或者甚至是框架中已经存在的东西)。
I would like to be able to do something like this:
我希望能够做这样的事情:
EventWaiter ew = new EventWaiter(button.Click);
ew.WaitOne();
EventWaiter ew2 = new EventWaiter(form.Closing);
ew2.WaitOne();
But I can't really find a way to construct such a class (I can't find a good valid way to pass the event as an argument). Can anyone help?
但是我真的找不到构造这样一个类的方法(我找不到一个好的有效方法来将事件作为参数传递)。任何人都可以帮忙吗?
To give an example of why this can be useful, consider something like this:
要举例说明为什么这很有用,请考虑以下内容:
var status = ShowStatusForm();
status.ShowInsertUsbStick();
bool cancelled = WaitForUsbStickOrCancel();
if(!cancelled){
status.ShowWritingOnUsbStick();
WriteOnUsbStick();
status.AskUserToRemoveUsbStick();
WaitForUsbStickToBeRemoved();
status.ShowFinished();
}else{
status.ShowCancelled();
}
status.WaitUntilUserPressesDone();
This is much more concise and readable than the equivalent code written with the logic spread out between many methods. But to implement WaitForUsbStickOrCancel(), WaitForUsbStickToBeRemoved and WaitUntilUserPressesDone() (assume that the we get an event when usb sticks are inserted or removed) I need to reimplement "EventWaiter" each time. Of course you have to be careful to never run this on the GUI-thread, but sometimes that is a worthwhile tradeoff for the simpler code.
这比用分布在许多方法之间的逻辑编写的等效代码更加简洁和可读。但是要实现WaitForUsbStickOrCancel()、WaitForUsbStickToBeRemoved 和WaitUntilUserPressesDone()(假设我们在插入或移除USB 棒时收到一个事件),我每次都需要重新实现“EventWaiter”。当然,您必须小心不要在 GUI 线程上运行它,但有时这对于更简单的代码来说是值得的权衡。
The alternative would look something like this:
替代方案如下所示:
var status = ShowStatusForm();
status.ShowInsertUsbStick();
usbHandler.Inserted += OnInserted;
status.Cancel += OnCancel;
//...
void OnInserted(/*..*/){
usbHandler.Inserted -= OnInserted;
status.ShowWritingOnUsbStick();
MethodInvoker mi = () => WriteOnUsbStick();
mi.BeginInvoke(WritingDone, null);
}
void WritingDone(/*..*/){
/* EndInvoke */
status.AskUserToRemoveUsbStick();
usbHandler.Removed += OnRemoved;
}
void OnRemoved(/*..*/){
usbHandler.Removed -= OnRemoved;
status.ShowFinished();
status.Done += OnDone;
}
/* etc */
I find that much harder to read. Admittedly, it is far from always that the flow will be so linear, but when it is, I like the first style.
我觉得这更难阅读。诚然,流程远非总是如此线性,但当它是时,我喜欢第一种风格。
It is comparable to using ShowMessage() and Form.ShowDialog() - they also block until some "event" occurs (though they will run a message-loop if they are called on the gui-thread).
它类似于使用 ShowMessage() 和 Form.ShowDialog() - 它们也会阻塞,直到发生某些“事件”(尽管如果在 gui 线程上调用它们,它们将运行消息循环)。
回答by Ricardo Villamil
Don't pass the event, pass a delegate that matches the event handler signature. This actually sounds hacky to me, so be aware of potential dead lock issues.
不要传递事件,传递与事件处理程序签名匹配的委托。这对我来说实际上听起来很hacky,所以要注意潜在的死锁问题。
回答by brutuscat
I think like these should work, didn't tried just coded.
我认为这些应该可以工作,没有尝试过只是编码。
public class EventWaiter<T> where T : EventArgs
{
private System.Threading.ManualResetEvent manualEvent;
public EventWaiter(T e)
{
manualEvent = new System.Threading.ManualResetEvent(false);
e += this.OnEvent;
}
public void OnEvent(object sender, EventArgs e)
{
manualEvent.Set();
}
public void WaitOne()
{
manualEvent.WaitOne();
}
public void Reset()
{
manualEvent.Reset();
}
}
Didn't thought about too much, but can't figure out how to make it isolated from the EventArgs.
没有想太多,但是想不出如何让它与EventArgs隔离。
Take a look at the MSDN ManualResetEventand you will discover that you can kind of chain the waits and so some weird stuff.
看看MSDN ManualResetEvent,你会发现你可以链接等待等等一些奇怪的东西。
回答by Dead.Rabit
I've rushed together a working sample in LinqPad using reflection, getting a reference to the EventInfo object with a string (be careful as you loose compile time checking). The obvious issue is that there is no guarentee an event will ever be fired, or that the event your expecting may be fired before the EventWaiter class is ready to start blocking so I'm not sure I'd sleep comfy if I put this in a production app.
我已经使用反射在 LinqPad 中匆匆忙忙地制作了一个工作示例,使用字符串获取对 EventInfo 对象的引用(当您放松编译时检查时要小心)。显而易见的问题是,没有保证会触发某个事件,或者在 EventWaiter 类准备好开始阻塞之前可能会触发您期望的事件,所以我不确定如果我把它放进去我会睡得很舒服一个生产应用程序。
void Main()
{
Console.WriteLine( "main thread started" );
var workerClass = new WorkerClassWithEvent();
workerClass.PerformWork();
var waiter = new EventWaiter( workerClass, "WorkCompletedEvent" );
waiter.WaitForEvent( TimeSpan.FromSeconds( 10 ) );
Console.WriteLine( "main thread continues after waiting" );
}
public class WorkerClassWithEvent
{
public void PerformWork()
{
var worker = new BackgroundWorker();
worker.DoWork += ( s, e ) =>
{
Console.WriteLine( "threaded work started" );
Thread.Sleep( 1000 ); // <= the work
Console.WriteLine( "threaded work complete" );
};
worker.RunWorkerCompleted += ( s, e ) =>
{
FireWorkCompletedEvent();
Console.WriteLine( "work complete event fired" );
};
worker.RunWorkerAsync();
}
public event Action WorkCompletedEvent;
private void FireWorkCompletedEvent()
{
if ( WorkCompletedEvent != null ) WorkCompletedEvent();
}
}
public class EventWaiter
{
private AutoResetEvent _autoResetEvent = new AutoResetEvent( false );
private EventInfo _event = null;
private object _eventContainer = null;
public EventWaiter( object eventContainer, string eventName )
{
_eventContainer = eventContainer;
_event = eventContainer.GetType().GetEvent( eventName );
}
public void WaitForEvent( TimeSpan timeout )
{
_event.AddEventHandler( _eventContainer, (Action)delegate { _autoResetEvent.Set(); } );
_autoResetEvent.WaitOne( timeout );
}
}
Output
输出
// main thread started
// threaded work started
// threaded work complete
// work complete event fired
// main thread continues after waiting
回答by Kalanj Djordje Djordje
I modified Dead.Rabit's class EventWaiter to handle EventHandler<T>
. So you can use for waiting all events type of EventHandler<T>
, that means your delegate is something like delegate void SomeDelegate(object sender, T EventsArgs)
.
我修改了 Dead.Rabit 的类 EventWaiter 来处理EventHandler<T>
. 因此,您可以用于等待所有事件类型EventHandler<T>
,这意味着您的委托类似于delegate void SomeDelegate(object sender, T EventsArgs)
.
public class EventWaiter<T>
{
private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
private EventInfo _event = null;
private object _eventContainer = null;
public EventWaiter(object eventContainer, string eventName)
{
_eventContainer = eventContainer;
_event = eventContainer.GetType().GetEvent(eventName);
}
public void WaitForEvent(TimeSpan timeout)
{
EventHandler<T> eventHandler = new EventHandler<T>((sender, args) => { _autoResetEvent.Set(); });
_event.AddEventHandler(_eventContainer, eventHandler);
_autoResetEvent.WaitOne(timeout);
_event.RemoveEventHandler(_eventContainer, eventHandler);
}
}
And for example I use that for waiting to get Url from HttpNotificationChannel when I registering to windows push notification service.
例如,当我注册到 Windows 推送通知服务时,我使用它来等待从 HttpNotificationChannel 获取 Url。
HttpNotificationChannel pushChannel = new HttpNotificationChannel(channelName);
//ChannelUriUpdated is event
EventWaiter<NotificationChannelUriEventArgs> ew = new EventWaiter<NotificationChannelUriEventArgs>(pushChannel, "ChannelUriUpdated");
pushChannel.Open();
ew.WaitForEvent(TimeSpan.FromSeconds(30));
回答by theres
You may also try this:
你也可以试试这个:
class EventWaiter<TEventArgs> where TEventArgs : EventArgs
{
private readonly Action<EventHandler<TEventArgs>> _unsubHandler;
private readonly Action<EventHandler<TEventArgs>> _subHandler;
public EventWaiter(Action<EventHandler<TEventArgs>> subHandler, Action<EventHandler<TEventArgs>> unsubHandler)
{
_unsubHandler = unsubHandler;
_subHandler = subHandler;
}
protected void Handler(object sender, TEventArgs args)
{
_unsubHandler.Invoke(Handler);
TaskCompletionSource.SetResult(args);
}
public TEventArgs WaitOnce()
{
TaskCompletionSource = new TaskCompletionSource<TEventArgs>();
_subHandler.Invoke(Handler);
return TaskCompletionSource.Task.Result;
}
protected TaskCompletionSource<TEventArgs> TaskCompletionSource { get; set; }
}
Usage:
用法:
EventArgs eventArgs = new EventWaiter<EventArgs>((h) => { button.Click += new EventHandler(h); }, (h) => { button.Click -= new EventHandler(h); }).WaitOnce();