非通用TaskCompletionSource或替代


73

我正在使用通常以异步方式显示的警报窗口(Telerik WPF)(打开时代码将继续运行),并且我想通过使用async / await使其同步。

我可以使用它,TaskCompletionSource但是该类是通用的,并且返回一个对象,就像Task<bool>我想要的只是一个Task没有返回值的普通对象一样。

public Task<bool> ShowAlert(object message, string windowTitle)
{
    var dialogParameters = new DialogParameters { Content = message };

    var tcs = new TaskCompletionSource<bool>();
    dialogParameters.Closed += (s, e) => tcs.TrySetResult(true);

    RadWindow.Alert(dialogParameters);

    return tcs.Task;
}

调用该方法的代码是

await MessageBoxService.ShowAlert("The alert text.")

我该如何返回一个非通用Task,其功能类似于dialogParameters.Closed事件触发之前可以等待的功能?我知道我可以忽略bool此代码中返回的那个。我正在寻找一个不同的解决方案。

Answers:


97

该方法可以更改为:

public Task ShowAlert(object message, string windowTitle)

Task<bool>从继承,Task所以您可以返回,Task<bool>而只暴露Task给调用者

编辑:

我找到了Microsoft文档http://www.microsoft.com/en-us/download/details.aspx?id=19957(作者:Stephen Toub),标题为“基于任务的异步模式”,该文档摘录了以下内容:同样的模式。

没有与TaskCompletionSource <TResult>对应的非通用副本。但是,Task <TResult>是从Task派生的,因此通用TaskCompletionSource <TResult>可用于I / O绑定方法,这些方法通过利用带有伪TResult的源简单地返回Task(布尔是一个很好的默认选择,并且如果开发人员担心Task的使用者将其向下转换为Task <TResult>,则可以使用私有TResult类型)


29
如果仅将void其接受为类型参数,则无需执行这些操作。
MgSam 2014年

4
objectover的好处bool(如Stephen的回答)bool可能意味着存在某种意义。
Matthijs Wessels 2014年

7
请考虑TaskCompletionSource <System.Reactive.Unit>,以使读者更容易理解。
RichB

5
@RichB有趣。我没看过那堂课,这让我感到奇怪,如果我和下一个家伙不知道这意味着什么,那真的很清楚吗?<wink>很好的建议。
凯文·卡利托夫斯基

3
Scala:scala-lang.org/api/current/index.html#scala.Unit F#:msdn.microsoft.com/en-us/library/dd483472.aspx Wikipedia:en.wikipedia.org/wiki/Unit_type 我认为是比多余的布尔值或对象更清晰,即使它会使读者阅读上述文档也是如此。
RichB 2014年

52

如果您不希望泄漏信息,则通常的方法是使用TaskCompletionSource<object>并完成结果null。然后将其作为返回Task


不幸的是,如果不知道泛型类型参数T,就无法创建SetCancel / SetException。这似乎是一个很大的设计错误。
Grigory 2014年

@格里高利:你没事吗?从今天开始,我可以毫无问题地使用SetException了。
塞巴斯蒂安2015年

1
@Sebastian:Task.FromException.NET 4.6中添加了非泛型。
Stephen Cleary

1
同意 这似乎是一个很大的设计错误。
Christian Findlay,2016年


3

Nito.AsyncEx实现了一个非通用TaskCompletionSource类,归功于上述@StephenCleary先生。


我刚刚下载了此存储库,然后从那里获取TaskCompletionSource类。这是有道理的,但是仍然是同样的问题。即使它确实返回了Task,您也无法使用async关键字标记您的方法,因此甚至无法使用await关键字。
Christian Findlay

1
@MelbourneDeveloper您正在谈论的内容与原始海报有所不同。TaskCompletionSource用于为事件等提供同步代码的异步接口。您可以等待TaskCompletionSource.Task罚款,所以不确定是什么问题?
georgiosd

仅供参考,TaskCompletionSourceAsyncEx库的v5版本中的非通用版本已删除:github.com/StephenCleary/AsyncEx/issues/176
mark.monteiro

1

来自@Kevin Kalitowski

我找到了Microsoft文档http://www.microsoft.com/en-us/download/details.aspx?id=19957,作者是Stephen Toub,标题为“基于任务的异步模式”。

凯文(Kevin)指出,该文档中有一个示例可以解决这个问题。这是示例:

public static Task Delay(int millisecondsTimeout)
{
    var tcs = new TaskCompletionSource<bool>();
    new Timer(self =>
    {
        ((IDisposable)self).Dispose();
        tcs.TrySetResult(true);
    }).Change(millisecondsTimeout, -1);
    return tcs.Task;
}

起初,我认为这不好,因为您不能在没有编译消息的情况下直接向该方法添加“异步”修饰符。但是,如果稍微更改方法,该方法将使用async / await进行编译:

public async static Task Delay(int millisecondsTimeout)
{
    var tcs = new TaskCompletionSource<bool>();
    new Timer(self =>
    {
        ((IDisposable)self).Dispose();
        tcs.TrySetResult(true);
    }).Change(millisecondsTimeout, -1);
    await tcs.Task;
}

编辑:起初我以为我已经过了驼峰。但是,当我在应用程序中运行等效代码时,此代码只会在等待tcs.Task;时使应用程序挂起。因此,我仍然认为这是async / await c#语法中的严重设计缺陷。


您的代码示例对我有用。不能真正在注释中发布代码,但我做了以下工作并await编辑GoAsync():private static async Task GoAsync() { Console.WriteLine($"Starting await at: {DateTime.Now.ToLongTimeString()}"); await Delay(3000); Console.WriteLine($"Got past await at: {DateTime.Now.ToLongTimeString()}"); }
Kevin Kalitowski

我的猜测是您的应用由于死锁而挂起:Timer和await都在同一线程上“运行”,因此Timer永远没有机会设置结果,而await永远无法完成。试试吧await tcs.Task.ConfigureAwait(false)
enzi

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.