等待抽象的异步任务

时间:2016-06-14 15:52:42

标签: c# async-await task abstract

我对async/await用法不熟悉。我试图在UI中有条件地抽象异步和await。我有一个抽象的基类:

public abstract class Base
{
    public abstract bool IsRunning { get; }
    public abstract Task<bool> Run();
}

从中得到一些派生实例,第一个是同步的:

internal class Derived1 : Base
{
    private readonly Base baseCase;
    private Task<bool> task;
    public Derived1(Base baseCase)
    {
        this.baseCase = baseCase;
    }
    public override bool IsRunning
    {
        get { return false; }
    }
    public override Task<bool> Run()
    {
        task = new Task<bool>(() => 
        {
            bool ok = DoSomething();
            return ok;
        });
        return task;
    }
}

和异步实现的派生类:

internal class Derived2 : Base
{
    private readonly Base baseCase;
    private Task<bool> task;
    public Derived2(Base baseCase)
    {
        this.baseCase = baseCase;
    }
    public override bool IsRunning
    {
        get { return task != null && task.Status == TaskStatus.Running; }
    }
    public override Task<bool> Run()
    {
        task = new Task<bool>(() => 
        {
            bool ok = DoSomething();
            return ok;
        });
        return task;
    }
}

然后在UI中,我想在await任务上asynchronous(如果用户在运行时配置中指定),如下所示:

internal class CaseMenuHandler
{
    private async void OnRun(object sender, EventArgs args)
    {
        foreach (var case in Cases)
        {
            Base baseCaseRunner = GetCaseRunner(case);
            try
            {
                bool ok = true;
                if( something_holds ) {
                    ok = await baseCaseRunner.Run();
                }
                else {
                    ok = baseCaseRunner.Run().Result;
                }
            }
            catch (Exception e)
            {
                LogError(...);
            }
        }
    }

希望这很清楚。我可以这样做,特别是在if块内有条件地等待吗?理想情况下,我希望Base类只返回bool而不是Task<bool>方法,并且只有Run类重写才能返回Derived2,但我不清楚如何做到这一点。也许我应该在Task<bool>的{​​{1}}方法中返回task.Result?如果有更好的方法,包括抽象或任何​​其他更正,请告诉我。欣赏任何想法。

编辑#1

以下回复中阐明了Run中同步实施的Derived2方法表单。我不允许更改Run方法的签名,因此,Derived1中的DoSomething方法(异步实现)现在看起来如下(感谢@ Stripling的评论) :

Run

编辑#2

当我尝试上述操作时(也尝试在Derived2定义后调用 public override async Task<bool> Run() { task = new Task<bool>(() => { bool ok = DoSomething(); return ok; }); task.Start(); return await task; } ,我收到以下错误:

task.Start()

5 个答案:

答案 0 :(得分:5)

  

我可以执行上述操作,特别是在if块内有条件地等待吗?

可以,但你不应该这样做。如果你做得对,那么以阻塞方式专门调用同步任务没有什么大的优势:作为一般规则,你可以只await返回的任务,如果是代表一个同步任务,然后await将以非常小的开销同步解决。

当我说'#34;如果你做得对,&#34;,这是正确的方法:

// synchronous
public override Task<bool> Run()
{
    var result = DoSomething();
    return Task.FromResult(result);
}


// asynchronous
public override async Task<bool> Run()
{
    var result = await DoSomethingAsync();
    return result;
}

await上面第一个例子的结果不会做任何线程切换或类似的事情。根据{{​​1}}的实施情况await第二个结果的结果DoSomethingAsync()。对抽象层没有特别需要:您始终可以检查Task是否已完成,await已完成的任务将始终返回一个值。

答案 1 :(得分:2)

我看不到你的同步版本是如何同步的,你仍然使用Task.Run(,我原本希望

internal class Derived1 : Base
{
    private readonly Base baseCase;
    private Task<bool> task;
    public Derived1(Base baseCase)
    {
        this.baseCase = baseCase;
    }
    public override bool IsRunning
    {
        get { return false; }    

    }

    public override Task<bool> Run()
    { 
        bool ok = DoSomething(); 
        return Task.FromResult(ok);
    }
}

如果你这样做而不是你这样做,你的其他代码就变成了

private async void OnRun(object sender, EventArgs args)
{
    foreach (var case in Cases)
    {
        Base baseCaseRunner = GetCaseRunner(case);
        try
        {
            bool ok = true;
            ok = await baseCaseRunner.Run();
        }
        catch (Exception e)
        {
            LogError(...);
        }
    }
}

异步版本将异步运行,同步版本将同步运行。

您的“异步版本”也不是真正的异步,请参阅Stripling's answer了解正确的方法。

答案 2 :(得分:2)

  

然后在UI中,我想等待异步任务(如果用户在运行时配置中指定)

首先,我不得不说这是一个非常糟糕的主意。有些东西应该是可配置的,有些则不应该。操作的异步不应该是可配置的。期。这是一个可怕的设计,我要做的第一件事是努力反击这样荒谬的“要求”。它实际上与是否抛出异常的可配置标志具有相同的意义。

那就是说,可以完成。在一天结束时,它很痛苦,难以维护,并且完全没用。但是,嘿,我认为你已经为此付出了代价,所以这一切都很好,嗯?

如果必须出于政治原因(绝对没有正当的技术原因),我建议您使用Boolean Argument Hack from my article on brownfield async。阻止(Result)的一个问题是,如果Run使用awaitreasons described on my blog),它就无效。

布尔参数hack只是添加一个布尔参数,指示该方法是否应该同步完成。

public abstract class Base
{
  public abstract Task<bool> RunAsync(bool sync);
}

此处的语义是,如果synctrue,则RunAsync 返回的任务必须已完成。

您的实现如下:

internal class Derived1 : Base
{
  public override async Task<bool> RunAsync(bool sync)
  {
    IsRunning = true;
    try
    {
      if (sync)
        return DoSomething();
      return await Task.Run(() => DoSomething());
    }
    finally
    {
      IsRunning = false;
    }
  }
}

它被称为:

private async void OnRun(object sender, EventArgs args)
{
  foreach (var case in Cases)
  {
    Base baseCaseRunner = GetCaseRunner(case);
    try
    {
      bool sync = !something_holds;
      bool ok = await baseCaseRunner.RunAsync(sync);
    }
    catch (Exception e)
    {
      LogError(...);
    }
  }
}

请注意,await始终可以调用 ,但如果OnRun为{synctrue实际上将同步 1}}。这是由于“等待快速路径” - 事实await first checks whether the task is already complete, and if it is, it continues synchronously(如我的异步介绍博客文章中所述)。

答案 3 :(得分:0)

最简单的方法是遵循标准:提供同步和异步方式:

public abstract class Base
{
    public abstract bool IsRunning { get; }
    public abstract bool Run();
    public abstract Task<bool> RunAsync();
}

public sealed class Implementer : Base 
{
    //... /*DoSomething returns a bool*/
    public override bool Run() { return DoSomething().Result; } //SYNC
    public override async Task<bool> RunAsync() { return await DoSomething();  } //ASYNC
    //...
}

答案 4 :(得分:0)

使用Task.FromResult返回包含同步计算结果的任务,然后等待。

bool ok = DoSomething();
return Task.FromResult(ok);

作为旁注,我不会将同步代码放在通常用于异步(或在其他类/位置同步)的方法中,反之亦然。我将为同步和异步实现使用不同的接口/基类。

interface IRunnable
{
    bool IsRunning;
    bool Run();
}

interface IRunnableAsync
{
    bool IsRunning;
    Task<bool> RunAsync();
}