【问题标题】:Await on abstracted asynchronous task等待抽象的异步任务
【发布时间】:2016-06-15 21:54:33
【问题描述】:

我对@9​​87654322@ 的用法非常陌生。我正在尝试在 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 中,我想在asynchronous 任务上执行await(如果用户在运行时配置中指定),如下所示:

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&lt;bool&gt; 用于Run 方法,并且只有Derived2 类覆盖返回Task&lt;bool&gt;,但我不清楚怎么做。也许我应该在Derived2Run 方法中返回task.Result?如果有更好的方法,包括抽象或任何​​其他更正,请告诉我。欣赏任何想法。

编辑#1

Derived1 中同步实现的Run 方法形式已在下面的回复中阐明。我不允许更改DoSomething 方法的签名,因此,我在Derived2(异步实现)中的Run 方法现在看起来如下(感谢@Stripling 的cmets):

    public override async Task<bool> Run()
    {
        task = new Task<bool>(() => 
        {
            bool ok = DoSomething();
            return ok;
        });
        task.Start();
        return await task;
    }

编辑#2

当我尝试上述方法时(也尝试在task 定义之后调用task.Start(),我收到以下错误:

Cross-thread operation not valid: Application accessed domain object from a thread other than a legal thread.

【问题讨论】:

  • 您能用高层次的术语解释一下您要做什么吗?
  • @YacoubMassad:让我试试。我试图有一个抽象类和一个派生类异步实现一些方法,而另一个派生类没有,然后在 UI 中根据我所处的情况有条件地等待。
  • WRT 编辑 #2:您正在尝试对只希望由特定线程访问的对象执行某些操作。这种事情经常发生在具有指定 UI 线程的应用程序中。如果不知道是哪一行代码导致了错误,就很难确切知道如何为您提供帮助。
  • @StriplingWarrior:我正在尝试进一步调试,但到目前为止,Edit #1 中的用法看起来还可以吗?

标签: c# async-await task abstract


【解决方案1】:

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

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

当我说“如果你做正确的事情”时,这是正确的方法:

// 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;
}

awaiting 上面第一个例子的结果不会做任何线程切换或类似的事情。 awaiting 第二个可能的结果,取决于DoSomethingAsync() 的实现。没有特别需要抽象层:您始终可以检查 Task 是否已完成,而 awaiting 已完成的任务将始终立即返回一个值。

【讨论】:

  • 谢谢,很高兴知道我可以在任何任务上使用 await。那么我什至不需要 if 条件。
  • 但是,如果对于您示例中的异步 Run 方法,我无法像您那样在 DoSomethingAsync 上使用 await (即,我不想使用 async 关键字而只返回 Task ) 因为我的任务是使用 lambda 函数定义的。我想我的问题是如果我需要使用 lambda 函数来实例化任务,如何使用 async/await 关键字?
  • 我在上面找到了我的问题的答案。请参阅我的编辑#1。我会对此进行测试并很快回来..谢谢
  • @squashed.bugaboo:有没有办法将导致错误的操作与执行长时间运行的操作的代码分开?另外,我猜你会想使用Task.Run(() =&gt; DoSomething())。这比创建一个新任务然后必须记住启动它要简单。
  • 我终于做到了,但不得不像你一样重构内部以隔离,StephenCleary 提到了同步组件。现在可以了。
【解决方案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 了解执行该方法的正确方法。

【讨论】:

    【解决方案3】:

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

    首先,我不得不说这是一个非常糟糕的主意。有些东西应该是可配置的,有些则不应该。操作的异步性不应该是可配置的。时期。这是一个可怕的设计,我要做的第一件事就是坚决反对像这样荒谬的“要求”。从字面上看,它与是否抛出异常的可配置标志具有相同的意义。

    也就是说,它可以完成。这很痛苦,难以维护,而且在一天结束时完全没用。但是,嘿,我假设你会为此获得报酬,所以这一切都很好,嗯?

    如果您出于政治原因必须这样做(绝​​对没有有效的技术原因),那么我建议您使用Boolean Argument Hack from my article on brownfield async。仅阻塞 (Result) 的一个问题是,如果 Run 使用 await(对于 reasons described on my blog)它不起作用。

    boolean 参数 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 一起调用,但如果synctrueOnRun 实际上将是同步。这是由于“等待快速路径”——await first checks whether the task is already complete, and if it is, it continues synchronously(如我的异步介绍博客文章中所述)这一事实。

    【讨论】:

    • 将 Task 对象作为类中的私有成员是不是一个坏主意?在我看到的所有示例中,就像您展示的那样,并且看起来您也能够捕获 IsRunning 状态。但这是否等同于拥有一个私有 Task 对象来检查 IsRunning 状态?也可以使用您的方法可能修复我在编辑#2中遇到的错误吗?从 Run 方法返回时发生错误。
    • @squashed.bugaboo: 有Tasks 作为成员并不是,而是有一个Task 成员来代表一个方法的执行类比较奇怪。这对我来说似乎没有必要。跨线程错误可能仍然会发生;如果您不能使用线程池线程(如错误所示),那么您需要将DoSomething 修改为异步(并采用bool sync 参数)。
    • @squashed.bugaboo:您似乎有两种不同的方法,正确的解决方案取决于哪种方法是必要的。 如果你有一个接口可以同步或异步实现,那么Task.FromResult是同步实现的一个很好的解决方案。 如果您有一个可配置参数,其中调用代码需要强制被调用代码是同步还是异步,那么布尔参数hack是合适的.
    • 我以为我修好了,但说得太早了。我什至剥离了 DoSomething 的所有内容,只是为了调试,并在 DoSomething 中简单地返回 true 只是为了调试,但即使这样也会导致在 Run() 方法中返回等待任务时立即出现跨线程错误。 DoSomething 方法的签名中没有异步。它必须吗?我们想要避免的一件事是将异步一直级联到所有被调用者
    • @squashed.bugaboo:如果DoSomething 没有代码,它应该不会导致跨线程错误。我建议您删除代码,直到您有一个最小的可重现测试用例,然后将其作为一个单独的问题发布。
    【解决方案4】:

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

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

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

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

    【讨论】:

    • task.Result 不是必须的,他可以像普通任务一样等待它并得到结果。
    猜你喜欢
    • 1970-01-01
    • 2014-03-18
    • 2014-09-06
    • 2013-02-10
    • 2017-07-22
    • 1970-01-01
    • 1970-01-01
    • 2019-04-08
    • 2014-10-01
    相关资源
    最近更新 更多