C# 如何等待事件单击

C# 如何等待事件单击,c#,async-await,asynccallback,C#,Async Await,Asynccallback,我正在等待一个按钮按下时触发的回调。重要的一点是,我想等待从一个简单的await回调,而不需要重塑代码。换句话说,我希望实现以下目标: internal async Task BatchLogic() { ProgressMessage = "Batch Logic Starts"; await OnCallbackFired(); ProgressMessage = "Batch Logic Ends"; } 我的尝试是 internal async Task Bat

我正在等待一个按钮按下时触发的回调。重要的一点是,我想等待从一个简单的
await
回调,而不需要重塑代码。换句话说,我希望实现以下目标:

internal async Task BatchLogic()
{
    ProgressMessage = "Batch Logic Starts";
    await OnCallbackFired();
    ProgressMessage = "Batch Logic Ends";
}
我的尝试是

internal async Task BatchLogic()
{
    ProgressMessage = "Batch Logic Starts";
    await Task.Factory.FromAsync(beginMethod, endMethod, state);
    ProgressMessage = "Batch Logic Ends";
}
定义如下

 private object state = null;
 private void endMethod(IAsyncResult obj)
 {
     IsBusy = false; 
 }
 private AsyncCallback callback;
 private IAsyncResult beginMethod(AsyncCallback callback, object state)
 {
     return Task.FromResult(true);
 }
按下按钮时,执行以下代码:

private async void RunNext()
{
    isBusy = true;
    await workToDo();
    isBusy = false; // the first 3 lines are not relevant
    callback = new AsyncCallback(endMethod);
    callback.Invoke(Task.FromResult(true));
}

问题是
endMethod
是从
回调调用的。Invoke
工厂.fromsync
从未返回,很可能是因为我不了解如何使用它,也没有找到与我试图实现的目标相对应的示例。

等待
什么,它需要是一个
任务
(这是一个过于简单化的问题,但这是重要的部分)(C#可以
等待
任何暴露
任务等待器GetAwaiter()方法的对象,但是95%的时间C#开发人员将花费
任务
/
任务
对象)

要创建
任务
,请使用
TaskCompletionSource
。这就是如何围绕其他概念性“任务”(如
IAsyncResult
样式的API、线程、重叠IO等)创建
Task
表示的方法

假设这是WinForms,则执行以下操作:

class MyForm : Form
{
    // Note that `TaskCompletionSource` must have a generic-type argument.
    // If you don't care about the result then use a dummy null `Object` or 1-byte `Boolean` value.
    private TaskCompletionSource<Button> tcs;

    private readonly Button button;

    public MyForm()
    {
        this.button = new Button() { Text = "Click me" };
        this.Controls.Add( this.button );
        this.button.Click += this.OnButtonClicked;
    }

    private void OnButtonClicked( Object sender, EventArgs e )
    {
        if( this.tcs == null ) return;

        this.tcs.SetResult( (Button)sender );
    }

    internal async Task BatchLogicAsync()
    {
        this.tcs = new TaskCompletionSource<Button>();

        ProgressMessage = "Batch Logic Starts";

        Button clickedButton = await this.tcs.Task;

        ProgressMessage = "Batch Logic Ends";
    }
}
类MyForm:Form
{
//请注意,`TaskCompletionSource`必须具有泛型类型参数。
//如果您不关心结果,则使用一个伪空'Object'或1字节'Boolean'值。
私有任务完成源tcs;
私人只读按钮;
公共MyForm()
{
this.button=new button(){Text=“Click me”};
this.Controls.Add(this.button);
this.button.Click+=this.onbutton单击;
}
私有void OnButtonClicked(对象发送方,事件参数e)
{
if(this.tcs==null)返回;
此.tcs.SetResult((按钮)发送器);
}
内部异步任务BatchLogicAsync()
{
this.tcs=新任务完成源();
ProgressMessage=“批处理逻辑启动”;
按钮clickedButton=等待this.tcs.Task;
ProgressMessage=“批处理逻辑结束”;
}
}

这是对我的初始代码的修复,使其按预期工作。 我必须为回调定义一个闭包

AsyncCallback callback;
然后我必须将
回调
beginMethod
传递给闭包:

private IAsyncResult beginMethod(AsyncCallback callback, object state)
{
    this.callback = callback;
    return Task.FromResult(true);
}
最后从事件中调用它(即WPF的MVVM中的
命令
方法)


下面是一个可以使用的扩展方法。取消订阅
Click
事件对于避免内存泄漏很重要,因为事件发布者保留事件订阅者的引用

public static Task OnClickAsync(this Control source)
{
    var tcs = new TaskCompletionSource<bool>();
    source.Click += OnClick;
    return tcs.Task;

    void OnClick(object sender, EventArgs e)
    {
        source.Click -= OnClick;
        tcs.SetResult(true);
    }
}

这是WPF、WinForms、UWP、WebForms还是其他什么?@Giulio是-MVVM框架以某种方式使用命令,而不是按钮事件。有些,如Caliburn.Micro,直接绑定到动作。WinForms和WPF都允许您编写异步事件处理程序,但本问题中的任何方法都没有事件处理程序签名。事实上,没有任何事件显示在code@Giulio在哪个框架内?问题不应该是“如何在FrameworkX中创建异步命令”?同样,这方面没有任何事件code@Giulio
TaskFactory
仅适用于使用现有的基于
IAsyncResult
的API时。您的代码没有做到这一点(您的代码似乎自己实现了
IAsyncResult
,这是问题的另一端)。
public static Task OnClickAsync(this Control source)
{
    var tcs = new TaskCompletionSource<bool>();
    source.Click += OnClick;
    return tcs.Task;

    void OnClick(object sender, EventArgs e)
    {
        source.Click -= OnClick;
        tcs.SetResult(true);
    }
}
await Button1.OnClickAsync();