使用async/await与Dispatcher.BeginInvoke()

36

我有一个方法,其中有一些代码执行了 await 操作:

public async Task DoSomething()
{
    var x = await ...;
}

我需要在Dispatcher线程上运行该代码。现在,Dispatcher.BeginInvoke()是可以等待执行的,但我不能将lambda标记为async,以便从其中运行await,像这样:

public async Task DoSomething()
{
    App.Current.Dispatcher.BeginInvoke(async () =>
        {
            var x = await ...;
        }
    );
}

在内部async中,我遇到了以下错误:

无法将 lambda 表达式转换为类型“System.Delegate”,因为它不是委托类型。

如何在Dispatcher.BeginInvoke()内使用async

4个回答

62

其他答案可能引入了一个晦涩的错误。以下是代码:

public async Task DoSomething()
{
    App.Current.Dispatcher.Invoke(async () =>
    {
        var x = await ...;
    });
}

使用Dispatcher.Invoke(Action callback)重载形式的Dispatcher.Invoke,在这种特殊情况下接受一个async void lambda表达式。这可能会导致非常意外的行为,就像通常发生在async void方法中一样。

你可能正在寻找类似于这样的东西:

public async Task<int> DoSomethingWithUIAsync()
{
    await Task.Delay(100);
    this.Title = "Hello!";
    return 42;
}

public async Task DoSomething()
{
    var x = await Application.Current.Dispatcher.Invoke<Task<int>>(
        DoSomethingWithUIAsync);
    Debug.Print(x.ToString()); // prints 42
}

在这种情况下,Dispatch.Invoke<Task<int>> 接受一个 Func<Task<int>> 参数,并返回相应的可等待的 Task<int>。如果你不需要从 DoSomethingWithUIAsync 返回任何内容,则只需使用 Task 而不是 Task<int>
或者,使用 Dispatcher.InvokeAsync 方法之一。

我该如何向"DoSomethingWithUIAsync"传递参数? - Cabuxa.Mapache
4
@Cabuxa.Mapache: Dispatcher.Invoke<Task<int>>(() => DoSomethingWithUIAsync(param)); - noseratio - open to work
1
@Noseratio 当你思考为什么 await Invoke(...) 起作用时,很明显它正是我们想要的。我只需要看到这个例子就“懂”了。谢谢! - ta.speot.is
1
你链接的MSDN文章关于异步最佳实践特别好。 - Thomas
@RobertSchmidt,Debug.Print() 将在 var x = await Application.Current.Dispatcher.Invoke ... 之前的同步上下文决定执行位置。如果没有,则会在随机池线程上执行。 - noseratio - open to work
显示剩余5条评论

-1

我认为你可以使用以下代码,然后根据情况选择使用异步和等待或不使用来触发和忘记:

public static Task FromUiThreadAsync(Action action)
{
    TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
    Dispatcher disp = GetUiDispatcher();
    
    disp.Invoke(DispatcherPriority.Background, new Action(() =>
    {
        try
        {
            action();
            tcs.SetResult(true);
        }
        catch (Exception ex)
        {
            tcs.SetException(ex);
        }
    }));

    return tcs.Task;
}

-2
使用Dispatcher.Invoke()
public async Task DoSomething()
{
    App.Current.Dispatcher.Invoke(async () =>
    {
        var x = await ...;
    });
}

5
为什么这个回答被踩了,有人能解释一下吗? - FosterZ
@JonathanGilbert,那个非常意外的行为具体是什么意思?请提供一些代码。 - Igor Buchelnikov
一个 async void 方法不会返回它启动的 Task,如果有的话。调用者甚至无法确定它是否是 async。如果你 Dispatcher.Invoke 一个 Func<Task>,那么调度程序会识别这种模式并观察 Task,但如果你 Dispatcher.Invoke 一个 async void,它实际上运行的是与同步方法相同的 Dispatcher.Invokeasync 实际上不是方法签名的一部分;它只是一个带有 void 返回类型的方法。因此,它被视为同步方法。 - Jonathan Gilbert
1
被接受的答案有一个关于async/await最佳实践的页面链接,其中指出:“一个微妙的陷阱是将async lambda传递给一个带有Action参数的方法;在这种情况下,async lambda返回void并继承了所有async void方法的问题。一般来说,只有在将async lambdas转换为返回Task的委托类型(例如Func<Task>)时才应使用它们。” - Jonathan Gilbert
1
我刚刚制作了一个示例并反编译了编译器的输出,以确保您是正确的:如果您调用的方法接受ActionFunc<Task>,那么编译器将调用Func<Task>重载--而且Dispatcher.Invoke确实都有。如果该方法仅具有Action,则您处于async void领域,但@noseriato的答案似乎是不正确的,至少在今天的C#和支持库中是如此。 :-) - Jonathan Gilbert
显示剩余4条评论

-2

(编辑:这个答案是错误的,但我很快会修复它)

声明这个

public async Task DoSomethingInUIThreadAsync(Func<Task> p)
{
  await Application.Current.Dispatcher.Invoke(p);
}  

使用方法如下


string someVar = "XXX";

DoSomethingInUIThreadAsync(()=>{

 await Task.Run(()=> {
     Thread.Sleep(10000);
     Button1.Text = someVar;
  });

});

DoSomethingInUIThreadAsync接收一个返回Task的委托,Application.Current.Dispatcher.Invoke接受一个可等待的Func回调。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接