C# 是否可以等待事件而不是另一个异步方法?

C# 是否可以等待事件而不是另一个异步方法?,c#,microsoft-metro,.net-4.5,async-await,windows-store-apps,C#,Microsoft Metro,.net 4.5,Async Await,Windows Store Apps,在我的C#/XAML metro应用程序中,有一个按钮启动一个长期运行的进程。因此,根据建议,我使用async/await来确保UI线程不会被阻塞: private async void Button_Click_1(object sender, RoutedEventArgs e) { await GetResults(); } private async Task GetResults() { // Do lot of complex stuff that take

在我的C#/XAML metro应用程序中,有一个按钮启动一个长期运行的进程。因此,根据建议,我使用async/await来确保UI线程不会被阻塞:

private async void Button_Click_1(object sender, RoutedEventArgs e) 
{
     await GetResults();
}

private async Task GetResults()
{ 
     // Do lot of complex stuff that takes a long time
     // (e.g. contact some web services)
  ...
}
有时候,GetResults中发生的事情需要额外的用户输入才能继续。为简单起见,假设用户只需单击“继续”按钮

我的问题是:如何暂停GetResults的执行,使其等待另一个按钮的单击等事件?

这里有一个丑陋的方法来实现我想要的:continue“按钮的事件处理程序设置了一个标志

private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
    _continue = true;
}
…并定期对其进行GetResults轮询:

 buttonContinue.Visibility = Visibility.Visible;
 while (!_continue) await Task.Delay(100);  // poll _continue every 100ms
 buttonContinue.Visibility = Visibility.Collapsed;
投票显然很糟糕(忙等待/浪费周期),我正在寻找基于事件的东西

有什么想法吗


顺便说一句,在这个简化的示例中,一个解决方案当然是拆分GetResults()分为两部分,从开始按钮调用第一部分,从继续按钮调用第二部分。实际上,GetResults中发生的事情更复杂,在执行过程中的不同点可能需要不同类型的用户输入。因此,将逻辑分解为多个方法将非常重要。

您可以使用n将的实例作为信号:

private SemaphoreSlim signal = new SemaphoreSlim(0, 1);

// set signal in event
signal.Release();

// wait for signal somewhere else
await signal.WaitAsync();

或者,您可以使用的实例创建表示按钮单击结果的:

private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

// complete task in event
tcs.SetResult(true);

// wait for task somewhere else
await tcs.Task;
private TaskCompletionSource tcs=new TaskCompletionSource();
//在事件中完成任务
tcs.SetResult(真);
//在其他地方等待任务
等待tcs任务;
理想情况下,您不需要。虽然您当然可以阻止异步线程,但这是一种资源浪费,而且不理想

考虑一个典型的例子,用户在等待点击按钮时去吃午饭

如果在等待用户输入时暂停了异步代码,那么在暂停线程时这只是在浪费资源

也就是说,如果在异步操作中,您将需要保持的状态设置为按钮已启用且单击时处于“等待”状态,则会更好。此时,您的
GetResults
方法停止

然后,单击按钮时,根据存储的状态启动另一个异步任务以继续工作

由于将在调用
GetResults
的事件处理程序中捕获(编译器将在使用
await
关键字的情况下执行此操作,并且假设您在UI应用程序中,该关键字应为非null),因此您可以这样使用:


ContinueToGetResultsAsync
是一种在按下按钮的情况下继续获取结果的方法。如果没有按下按钮,则事件处理程序将不执行任何操作。

当您需要等待一件不寻常的事情时,最简单的答案通常是
TaskCompletionSource
(或一些基于
TaskCompletionSource
async
启用的原语)

在这种情况下,您的需求非常简单,因此您可以直接使用
TaskCompletionSource

private TaskCompletionSource<object> continueClicked;

private async void Button_Click_1(object sender, RoutedEventArgs e) 
{
  // Note: You probably want to disable this button while "in progress" so the
  //  user can't click it twice.
  await GetResults();
  // And re-enable the button here, possibly in a finally block.
}

private async Task GetResults()
{ 
  // Do lot of complex stuff that takes a long time
  // (e.g. contact some web services)

  // Wait for the user to click Continue.
  continueClicked = new TaskCompletionSource<object>();
  buttonContinue.Visibility = Visibility.Visible;
  await continueClicked.Task;
  buttonContinue.Visibility = Visibility.Collapsed;

  // More work...
}

private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
  if (continueClicked != null)
    continueClicked.TrySetResult(null);
}
private TaskCompletionSource继续选中;
专用异步无效按钮\u单击\u 1(对象发送方,路由目标)
{
//注意:您可能希望在“进行中”时禁用此按钮,以便
//用户不能单击它两次。
等待GetResults();
//在这里重新启用按钮,可能在finally块中。
}
私有异步任务GetResults()
{ 
//做很多需要很长时间的复杂事情
//(例如,联系一些web服务)
//等待用户单击“继续”。
continueClicked=新建TaskCompletionSource();
按钮continue.Visibility=可见性.Visibility;
等待继续完成任务;
buttonContinue.Visibility=可见性.已折叠;
//更多的工作。。。
}
私有无效按钮继续单击(对象发送者,路由目标)
{
if(continueClicked!=null)
continueClicked.TrySetResult(null);
}

从逻辑上讲,
TaskCompletionSource
类似于一个
async
ManualResetEvent
,只是您只能“设置”事件一次,事件可以有一个“结果”(在这种情况下,我们不使用它,所以我们只将结果设置为
null
).

Stephen Toub发布了这个
AsyncManualResetEvent

公共类异步手动重置事件
{ 
私有易失性TaskCompletionSource m_tcs=新TaskCompletionSource();
公共任务WaitAsync(){return m_tcs.Task;}
公共无效集()
{ 
var tcs=m_tcs;
Task.Factory.StartNew(s=>((TaskCompletionSource)s).TrySetResult(true),
tcs、CancellationToken.None、TaskCreationOptions.PreferFairity、TaskScheduler.Default);
Task.Wait();
}
公共无效重置()
{ 
while(true)
{ 
var tcs=m_tcs;
如果(!tcs.Task.IsCompleted | |
Interlocked.CompareExchange(ref m_tcs,new TaskCompletionSource(),tcs)=tcs)
返回;
} 
} 
}

以下是我使用的实用程序类:

public class AsyncEventListener
{
    private readonly Func<bool> _predicate;

    public AsyncEventListener() : this(() => true)
    {

    }

    public AsyncEventListener(Func<bool> predicate)
    {
        _predicate = predicate;
        Successfully = new Task(() => { });
    }

    public void Listen(object sender, EventArgs eventArgs)
    {
        if (!Successfully.IsCompleted && _predicate.Invoke())
        {
            Successfully.RunSynchronously();
        }
    }

    public Task Successfully { get; }
}

简单助手类:

public class EventAwaiter<TEventArgs>
{
    private readonly TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>();

    private readonly Action<EventHandler<TEventArgs>> _unsubscribe;

    public EventAwaiter(Action<EventHandler<TEventArgs>> subscribe, Action<EventHandler<TEventArgs>> unsubscribe)
    {
        subscribe(Subscription);
        _unsubscribe = unsubscribe;
    }

    public Task<TEventArgs> Task => _eventArrived.Task;

    private EventHandler<TEventArgs> Subscription => (s, e) =>
        {
            _eventArrived.TrySetResult(e);
            _unsubscribe(Subscription);
        };
}
var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(
                            h => example.YourEvent += h,
                            h => example.YourEvent -= h);
await valueChangedEventAwaiter.Task;
    private static event EventHandler<EventArgs> _testEvent;

    private static async Task Main()
    {
        var eventObservable = Observable
            .FromEventPattern<EventArgs>(
                h => _testEvent += h,
                h => _testEvent -= h);

        Task.Delay(5000).ContinueWith(_ => _testEvent?.Invoke(null, new EventArgs()));

        var res = await eventObservable.FirstAsync();

        Console.WriteLine("Event got fired");
    }
公共类事件等待器
{
私有只读TaskCompletionSource_eventArrived=新TaskCompletionSource();
私人只读操作_取消订阅;
公共事件等待者(操作订阅、操作取消订阅)
{
认购(认购);
_退订=退订;
}
公共任务任务=>\u eventArrived.Task;
私有EventHandler订阅=>(s,e)=>
{
_事件到达。TrySetResult(e);
_退订(认购);
};
}
用法:

public class EventAwaiter<TEventArgs>
{
    private readonly TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>();

    private readonly Action<EventHandler<TEventArgs>> _unsubscribe;

    public EventAwaiter(Action<EventHandler<TEventArgs>> subscribe, Action<EventHandler<TEventArgs>> unsubscribe)
    {
        subscribe(Subscription);
        _unsubscribe = unsubscribe;
    }

    public Task<TEventArgs> Task => _eventArrived.Task;

    private EventHandler<TEventArgs> Subscription => (s, e) =>
        {
            _eventArrived.TrySetResult(e);
            _unsubscribe(Subscription);
        };
}
var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(
                            h => example.YourEvent += h,
                            h => example.YourEvent -= h);
await valueChangedEventAwaiter.Task;
    private static event EventHandler<EventArgs> _testEvent;

    private static async Task Main()
    {
        var eventObservable = Observable
            .FromEventPattern<EventArgs>(
                h => _testEvent += h,
                h => _testEvent -= h);

        Task.Delay(5000).ContinueWith(_ => _testEvent?.Invoke(null, new EventArgs()));

        var res = await eventObservable.FirstAsync();

        Console.WriteLine("Event got fired");
    }
var valuechangedventawer=新事件等待者(
h=>example.YourEvent+=h,
h=>example.YourEvent-=h
    private static event EventHandler<EventArgs> _testEvent;

    private static async Task Main()
    {
        var eventObservable = Observable
            .FromEventPattern<EventArgs>(
                h => _testEvent += h,
                h => _testEvent -= h);

        Task.Delay(5000).ContinueWith(_ => _testEvent?.Invoke(null, new EventArgs()));

        var res = await eventObservable.FirstAsync();

        Console.WriteLine("Event got fired");
    }
public delegate Task AsyncEventHandler<T>(object sender, T args) where T : EventArgs;

public class AsyncEvent : AsyncEvent<EventArgs>
{
    public AsyncEvent() : base()
    {
    }
}

public class AsyncEvent<T> where T : EventArgs
{
    private readonly HashSet<AsyncEventHandler<T>> _handlers;

    public AsyncEvent()
    {
        _handlers = new HashSet<AsyncEventHandler<T>>();
    }

    public void Add(AsyncEventHandler<T> handler)
    {
        _handlers.Add(handler);
    }

    public void Remove(AsyncEventHandler<T> handler)
    {
        _handlers.Remove(handler);
    }

    public async Task InvokeAsync(object sender, T args)
    {
        foreach (var handler in _handlers)
        {
            await handler(sender, args);
        }
    }

    public static AsyncEvent<T> operator+(AsyncEvent<T> left, AsyncEventHandler<T> right)
    {
        var result = left ?? new AsyncEvent<T>();
        result.Add(right);
        return result;
    }

    public static AsyncEvent<T> operator-(AsyncEvent<T> left, AsyncEventHandler<T> right)
    {
        left.Remove(right);
        return left;
    }
}
public AsyncEvent MyNormalEvent;
public AsyncEvent<ProgressEventArgs> MyCustomEvent;
if (MyNormalEvent != null) await MyNormalEvent.InvokeAsync(this, new EventArgs());
if (MyCustomEvent != null) await MyCustomEvent.InvokeAsync(this, new ProgressEventArgs());
MyControl.Click += async (sender, args) => {
    // await...
}

MyControl.Click += (sender, args) => {
    // synchronous code
    return Task.CompletedTask;
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(
    Action<EventHandler> addHandler,
    Action<EventHandler> removeHandler)
{
    var tcs = new TaskCompletionSource<object>();
    addHandler(Handler);
    return tcs.Task;

    void Handler(object sender, EventArgs e)
    {
        removeHandler(Handler);
        tcs.SetResult(null);
    }
}

/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
    Action<EventHandler<TEventArgs>> addHandler,
    Action<EventHandler<TEventArgs>> removeHandler)
{
    var tcs = new TaskCompletionSource<TEventArgs>();
    addHandler(Handler);
    return tcs.Task;

    void Handler(object sender, TEventArgs e)
    {
        removeHandler(Handler);
        tcs.SetResult(e);
    }
}

/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on a supplied event delegate type, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TDelegate, TEventArgs>(
    Action<TDelegate> addHandler, Action<TDelegate> removeHandler)
{
    var tcs = new TaskCompletionSource<TEventArgs>();
    TDelegate handler = default;
    Action<object, TEventArgs> genericHandler = (sender, e) =>
    {
        removeHandler(handler);
        tcs.SetResult(e);
    };
    handler = (TDelegate)(object)genericHandler.GetType().GetMethod("Invoke")
        .CreateDelegate(typeof(TDelegate), genericHandler);
    addHandler(handler);
    return tcs.Task;
}

/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(object target, string eventName)
{
    var type = target.GetType();
    var eventInfo = type.GetEvent(eventName);
    if (eventInfo == null) throw new InvalidOperationException("Event not found.");
    var tcs = new TaskCompletionSource<object>();
    EventHandler handler = default;
    handler = new EventHandler((sender, e) =>
    {
        eventInfo.RemoveEventHandler(target, handler);
        tcs.SetResult(null);
    });
    eventInfo.AddEventHandler(target, handler);
    return tcs.Task;
}

/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
    object target, string eventName)
{
    var type = target.GetType();
    var eventInfo = type.GetEvent(eventName);
    if (eventInfo == null) throw new InvalidOperationException("Event not found.");
    var tcs = new TaskCompletionSource<TEventArgs>();
    EventHandler<TEventArgs> handler = default;
    handler = new EventHandler<TEventArgs>((sender, e) =>
    {
        eventInfo.RemoveEventHandler(target, handler);
        tcs.SetResult(e);
    });
    eventInfo.AddEventHandler(target, handler);
    return tcs.Task;
}

/// <summary>Converts a generic Action-based .NET event to a Task.</summary>
public static Task<TArgument> EventActionToAsync<TArgument>(
    Action<Action<TArgument>> addHandler,
    Action<Action<TArgument>> removeHandler)
{
    var tcs = new TaskCompletionSource<TArgument>();
    addHandler(Handler);
    return tcs.Task;

    void Handler(TArgument arg)
    {
        removeHandler(Handler);
        tcs.SetResult(arg);
    }
}
var p = new Progress<int>();

//...

int result = await EventToAsync<int>(
    h => p.ProgressChanged += h, h => p.ProgressChanged -= h);

// ...or...

int result = await EventToAsync<EventHandler<int>, int>(
    h => p.ProgressChanged += h, h => p.ProgressChanged -= h);

// ...or...

int result = await EventToAsync<int>(p, "ProgressChanged");
public static event Action<int> MyEvent;

//...

int result = await EventActionToAsync<int>(h => MyEvent += h, h => MyEvent -= h);