我如何等待C#中的事件?
我正在创建一个包含一系列事件的类,其中一个事件是我如何等待C#中的事件?,c#,events,asynchronous,C#,Events,Asynchronous,我正在创建一个包含一系列事件的类,其中一个事件是GameShuttingDown。触发此事件时,我需要调用事件处理程序。此事件的目的是通知用户游戏正在关闭,他们需要保存数据。拯救是可以等待的,而事件不是。因此,当处理程序被调用时,游戏会在等待处理程序完成之前关闭 public event EventHandler<EventArgs> GameShuttingDown; public virtual async Task ShutdownGame() { await thi
GameShuttingDown
。触发此事件时,我需要调用事件处理程序。此事件的目的是通知用户游戏正在关闭,他们需要保存数据。拯救是可以等待的,而事件不是。因此,当处理程序被调用时,游戏会在等待处理程序完成之前关闭
public event EventHandler<EventArgs> GameShuttingDown;
public virtual async Task ShutdownGame()
{
await this.NotifyGameShuttingDown();
await this.SaveWorlds();
this.NotifyGameShutDown();
}
private async Task SaveWorlds()
{
foreach (DefaultWorld world in this.Worlds)
{
await this.worldService.SaveWorld(world);
}
}
protected virtual void NotifyGameShuttingDown()
{
var handler = this.GameShuttingDown;
if (handler == null)
{
return;
}
handler(this, new EventArgs());
}
我知道事件的签名是void EventName
,因此,使其异步基本上是一个fire-and-forget过程。我的引擎大量使用事件来通知第三方开发人员(和多个内部组件)引擎中正在发生的事件,并让他们对这些事件做出反应
有没有一个好的方法可以用我可以使用的基于异步的东西来代替事件处理?我不确定是否应该使用beginshutdownggame
和endshutdownggame
进行回调,但这很痛苦,因为只有调用源才能传递回调,而没有任何插入引擎的第三方内容,这正是我通过事件得到的。如果服务器调用game.shutdownggame()
,引擎插件和引擎中的其他组件将无法传递回调,除非我建立某种注册方法,保留回调集合
如果您有任何关于首选/推荐路线的建议,我们将不胜感激!我环顾四周,大部分我看到的是使用开始/结束方法,我认为这不会满足我想要做的事情
编辑
我正在考虑的另一个选择是使用注册方法,它接受等待的回调。我迭代所有回调,抓取它们的任务,然后等待whalll
private List<Func<Task>> ShutdownCallbacks = new List<Func<Task>>();
public void RegisterShutdownCallback(Func<Task> callback)
{
this.ShutdownCallbacks.Add(callback);
}
public async Task Shutdown()
{
var callbackTasks = new List<Task>();
foreach(var callback in this.ShutdownCallbacks)
{
callbackTasks.Add(callback());
}
await Task.WhenAll(callbackTasks);
}
private List ShutdownCallbacks=new List();
公共无效注册表shutdowncallback(Func回调)
{
this.ShutdownCallbacks.Add(callback);
}
公共异步任务关闭()
{
var callbackTasks=新列表();
foreach(此.ShutdownCallbacks中的var回调)
{
添加(callback());
}
等待任务。WhenAll(回调任务);
}
没错,事件本身是不可等待的,所以你必须解决它
我过去使用过的一种解决方案是,等待其中的所有条目被发布。在我的情况下,我只有一个订阅的事件,因此我可以将其硬编码为new SemaphoreSlim(0,1)
,但在您的情况下,您可能希望覆盖事件的getter/setter,并保留一个有多少订阅者的计数器,以便动态设置最大并发线程数
之后,您将一个信号量条目传递给每个订阅者,并让他们做自己的事情,直到SemaphoreSlim.CurrentCount==amountOfSubscribers
(又名:所有点都被释放)
这实际上会阻止您的程序,直到所有事件订阅服务器都完成
你可能还想考虑为你的订阅服务器提供一个事件La<代码> GAMESHUTHO胡完成,当他们完成游戏任务时,他们必须调用。结合
SemaphoreSlim.Release(int)
重载,您现在可以清除所有信号量条目,只需使用semaphore.Wait()
来阻止线程。现在,您不必检查是否已清除所有条目,而是等待一个点被释放(但应该只有一个时刻所有点都被释放)。的确,事件本身是不可等待的,因此您必须解决它
我过去使用过的一种解决方案是,等待其中的所有条目被发布。在我的情况下,我只有一个订阅的事件,因此我可以将其硬编码为new SemaphoreSlim(0,1)
,但在您的情况下,您可能希望覆盖事件的getter/setter,并保留一个有多少订阅者的计数器,以便动态设置最大并发线程数
之后,您将一个信号量条目传递给每个订阅者,并让他们做自己的事情,直到SemaphoreSlim.CurrentCount==amountOfSubscribers
(又名:所有点都被释放)
这实际上会阻止您的程序,直到所有事件订阅服务器都完成
你可能还想考虑为你的订阅服务器提供一个事件La<代码> GAMESHUTHO胡完成,当他们完成游戏任务时,他们必须调用。结合
SemaphoreSlim.Release(int)
重载,您现在可以清除所有信号量条目,只需使用semaphore.Wait()
来阻止线程。现在,您不必检查是否已清除所有条目,而是等待一个点被释放(但应该只有一个时刻所有点都被释放)。就个人而言,我认为使用async
事件处理程序可能不是最佳的设计选择,不是最不重要的原因就是你的问题。对于同步处理程序,很容易知道它们何时完成
public event EventHandler<EventArgs> GameShuttingDown;
public virtual async Task ShutdownGame()
{
await this.NotifyGameShuttingDown();
await this.SaveWorlds();
this.NotifyGameShutDown();
}
private async Task SaveWorlds()
{
foreach (DefaultWorld world in this.Worlds)
{
await this.worldService.SaveWorld(world);
}
}
protected virtual void NotifyGameShuttingDown()
{
var handler = this.GameShuttingDown;
if (handler == null)
{
return;
}
handler(this, new EventArgs());
}
也就是说,如果出于某种原因,您必须或至少是被迫坚持这种设计,您可以用一种友好的方式来完成
您注册处理程序并等待它们的想法很好。但是,我建议坚持使用现有的事件范例,因为这样可以保持代码中事件的表达能力。最重要的是,您必须偏离基于标准EventHandler
的委托类型,并使用返回任务的委托类型,以便等待处理程序
这里有一个简单的例子来说明我的意思:
class A
{
public event Func<object, EventArgs, Task> Shutdown;
public async Task OnShutdown()
{
Func<object, EventArgs, Task> handler = Shutdown;
if (handler == null)
{
return;
}
Delegate[] invocationList = handler.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, EventArgs, Task>)invocationList[i])(this, EventArgs.Empty);
}
await Task.WhenAll(handlerTasks);
}
}
看完这个例子后,我现在想知道是否有一种方法可以让C#稍微抽象一下。也许这太复杂了
class Program
{
static void Main(string[] args)
{
A a = new A();
a.Shutdown += Handler1;
a.Shutdown += Handler2;
a.Shutdown += Handler3;
a.OnShutdown().Wait();
}
static async Task Handler1(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #1");
await Task.Delay(1000);
Console.WriteLine("Done with shutdown handler #1");
}
static async Task Handler2(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #2");
await Task.Delay(5000);
Console.WriteLine("Done with shutdown handler #2");
}
static async Task Handler3(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #3");
await Task.Delay(2000);
Console.WriteLine("Done with shutdown handler #3");
}
}
class A
{
public delegate void ShutdownEventHandler(EventArgs e);
public event ShutdownEventHandler ShutdownEvent;
public void OnShutdownEvent(EventArgs e)
{
ShutdownEventHandler handler = ShutdownEvent;
if (handler == null) { return; }
Delegate[] invocationList = handler.GetInvocationList();
Parallel.ForEach<Delegate>(invocationList,
(hndler) => { ((ShutdownEventHandler)hndler)(e); });
}
}
using System;
using System.Threading;
using System.Threading.Tasks;
...
class Program
{
static void Main(string[] args)
{
A a = new A();
a.ShutdownEvent += Handler1;
a.ShutdownEvent += Handler2;
a.ShutdownEvent += Handler3;
a.OnShutdownEvent(new EventArgs());
Console.WriteLine("Handlers should all be done now.");
Console.ReadKey();
}
static void handlerCore( int id, int offset, int num )
{
Console.WriteLine("Starting shutdown handler #{0}", id);
int step = 200;
Thread.Sleep(offset);
for( int i = 0; i < num; i += step)
{
Thread.Sleep(step);
Console.WriteLine("...Handler #{0} working - {1}/{2}", id, i, num);
}
Console.WriteLine("Done with shutdown handler #{0}", id);
}
static void Handler1(EventArgs e) { handlerCore(1, 7, 5000); }
static void Handler2(EventArgs e) { handlerCore(2, 5, 3000); }
static void Handler3(EventArgs e) { handlerCore(3, 3, 1000); }
}
internal static class EventExtensions
{
public static void InvokeAsync<TEventArgs>(this EventHandler<TEventArgs> @event, object sender,
TEventArgs args, AsyncCallback ar, object userObject = null)
where TEventArgs : class
{
var listeners = @event.GetInvocationList();
foreach (var t in listeners)
{
var handler = (EventHandler<TEventArgs>) t;
handler.BeginInvoke(sender, args, ar, userObject);
}
}
}
public event EventHandler<CodeGenEventArgs> CodeGenClick;
private void CodeGenClickAsync(CodeGenEventArgs args)
{
CodeGenClick.InvokeAsync(this, args, ar =>
{
InvokeUI(() =>
{
if (args.Code.IsNotNullOrEmpty())
{
var oldValue = (string) gv.GetRowCellValue(gv.FocusedRowHandle, nameof(License.Code));
if (oldValue != args.Code)
gv.SetRowCellValue(gv.FocusedRowHandle, nameof(License.Code), args.Code);
}
});
});
}
public static class AsynchronousEventExtensions
{
public static Task Raise<TSource, TEventArgs>(this Func<TSource, TEventArgs, Task> handlers, TSource source, TEventArgs args)
where TEventArgs : EventArgs
{
if (handlers != null)
{
return Task.WhenAll(handlers.GetInvocationList()
.OfType<Func<TSource, TEventArgs, Task>>()
.Select(h => h(source, args)));
}
return Task.CompletedTask;
}
}
public event Func<A, EventArgs, Task> Shutdown;
private async Task SomeMethod()
{
...
await Shutdown.Raise(this, EventArgs.Empty);
...
}
someInstance.Shutdown += OnShutdown1;
someInstance.Shutdown += OnShutdown2;
...
private async Task OnShutdown1(SomeClass source, MyEventArgs args)
{
if (!args.IsProcessed)
{
// An operation
await Task.Delay(123);
args.IsProcessed = true;
}
}
private async Task OnShutdown2(SomeClass source, MyEventArgs args)
{
// OnShutdown2 will start execution the moment OnShutdown1 hits await
// and will proceed to the operation, which is not the desired behavior.
// Or it can be just a concurrent DB query using the same connection
// which can result in an exception thrown base on the provider
// and connection string options
if (!args.IsProcessed)
{
// An operation
await Task.Delay(123);
args.IsProcessed = true;
}
}
public static class AsynchronousEventExtensions
{
public static async Task Raise<TSource, TEventArgs>(this Func<TSource, TEventArgs, Task> handlers, TSource source, TEventArgs args)
where TEventArgs : EventArgs
{
if (handlers != null)
{
foreach (Func<TSource, TEventArgs, Task> handler in handlers.GetInvocationList())
{
await handler(source, args);
}
}
}
}
public delegate Task AsyncEventHandler(AsyncEventArgs e);
public class AsyncEventArgs : System.EventArgs
{
public bool Handled { get; set; }
}
public class AsyncEvent
{
private string name;
private List<AsyncEventHandler> handlers;
private Action<string, Exception> errorHandler;
public AsyncEvent(string name, Action<string, Exception> errorHandler)
{
this.name = name;
this.handlers = new List<AsyncEventHandler>();
this.errorHandler = errorHandler;
}
public void Register(AsyncEventHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Add(handler);
}
public void Unregister(AsyncEventHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Remove(handler);
}
public IReadOnlyList<AsyncEventHandler> Handlers
{
get
{
var temp = default(AsyncEventHandler[]);
lock (this.handlers)
temp = this.handlers.ToArray();
return temp.ToList().AsReadOnly();
}
}
public async Task InvokeAsync()
{
var ev = new AsyncEventArgs();
var exceptions = new List<Exception>();
foreach (var handler in this.Handlers)
{
try
{
await handler(ev).ConfigureAwait(false);
if (ev.Handled)
break;
}
catch(Exception ex)
{
exceptions.Add(ex);
}
}
if (exceptions.Any())
this.errorHandler?.Invoke(this.name, new AggregateException(exceptions));
}
}
public class MyGame
{
private AsyncEvent _gameShuttingDown;
public event AsyncEventHandler GameShuttingDown
{
add => this._gameShuttingDown.Register(value);
remove => this._gameShuttingDown.Unregister(value);
}
void ErrorHandler(string name, Exception ex)
{
// handle event error.
}
public MyGame()
{
this._gameShuttingDown = new AsyncEvent("GAME_SHUTTING_DOWN", this.ErrorHandler);.
}
}
internal async Task NotifyGameShuttingDownAsync()
{
await this._gameShuttingDown.InvokeAsync().ConfigureAwait(false);
}
public delegate Task AsyncEventHandler<in T>(T e) where T : AsyncEventArgs;
public class AsyncEvent<T> where T : AsyncEventArgs
{
private string name;
private List<AsyncEventHandler<T>> handlers;
private Action<string, Exception> errorHandler;
public AsyncEvent(string name, Action<string, Exception> errorHandler)
{
this.name = name;
this.handlers = new List<AsyncEventHandler<T>>();
this.errorHandler = errorHandler;
}
public void Register(AsyncEventHandler<T> handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Add(handler);
}
public void Unregister(AsyncEventHandler<T> handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this.handlers)
this.handlers.Remove(handler);
}
public IReadOnlyList<AsyncEventHandler<T>> Handlers
{
get
{
var temp = default(AsyncEventHandler<T>[]);
lock (this.handlers)
temp = this.handlers.ToArray();
return temp.ToList().AsReadOnly();
}
}
public async Task InvokeAsync(T ev)
{
var exceptions = new List<Exception>();
foreach (var handler in this.Handlers)
{
try
{
await handler(ev).ConfigureAwait(false);
if (ev.Handled)
break;
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
if (exceptions.Any())
this.errorHandler?.Invoke(this.name, new AggregateException(exceptions));
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Example
{
// delegate as alternative standard EventHandler
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e, CancellationToken token);
public class ExampleObject
{
// use as regular event field
public event AsyncEventHandler<EventArgs> AsyncEvent;
// invoke using the extension method
public async Task InvokeEventAsync(CancellationToken token) {
await this.AsyncEvent.InvokeAsync(this, EventArgs.Empty, token);
}
// subscribe (add a listener) with regular syntax
public static async Task UsageAsync() {
var item = new ExampleObject();
item.AsyncEvent += (sender, e, token) => Task.CompletedTask;
await item.InvokeEventAsync(CancellationToken.None);
}
}
public static class AsynEventHandlerExtensions
{
// invoke a async event (with null-checking)
public static async Task InvokeAsync<TEventArgs>(this AsyncEventHandler<TEventArgs> handler, object sender, TEventArgs args, CancellationToken token) {
var delegates = handler?.GetInvocationList();
if (delegates?.Length > 0) {
var tasks = delegates
.Cast<AsyncEventHandler<TEventArgs>>()
.Select(e => e.Invoke(sender, args, token));
await Task.WhenAll(tasks);
}
}
}
}