如何等待覆盖异步函数?

4
我们有一个通用的Job类,其中包含一个抽象的HeavyTask方法,如下所示:
abstract class Job {
    private Task m_task; 
    protected abstract void HeavyTask(); 

    public void StartJob(){
        m_task = Task.Run(() => HeavyTask());
    }
    public async Task WaitJob(){
        await m_task; 
    }
}

而派生类则重写 HeavyTask 函数并将其变成异步的:

class JobFoo : Job {
    protected override async void HeavyTask()
    {
        await Task.Delay(1000);
        Debug.WriteLine("JobFoo is done");
    }
}

当我们使用这种方法时,似乎没有等待 HeavyTask() 的完成:

Job job = new JobFoo();
job.StartJob();
await job.WaitJob();
Debug.WriteLine("All Done");

输出:

全部完成
JobFoo 已完成

如果我们不对重写的 HeavyTask 使用 async,那么它会按预期工作。但是我不能保证覆盖 Job 的人不会将 HeavyTask 设为 async。 我想知道为什么它没有成功等待,是否有方法确保它被等待? 如果可以的话,您能否解释一下像上面展示的将非异步函数重写为异步函数是否是一个好习惯呢?


7
旧的异步无返回值问题。不要使用 protected abstract void HeavyTask();,请使用 protected abstract Task HeavyTask(); - xZ6a33YaYEfmv
为什么你有单独的启动和等待方法?看起来你应该只有一个名为StartJob的方法,它返回一个Task,并让每个子类决定如何创建任务。 - Lee
@EhsanSajjad,那不是OP所询问的方法。 - i3arnon
可能是async/await - when to return a Task vs void?的重复问题。 - xZ6a33YaYEfmv
你好@李,感谢你指出这一点。是的,你完全正确。这里不需要单独的“WaitJob”函数。 - Yuchen
2个回答

11

这个方法没有等待是因为没有可等待的对象(例如Task)可供等待。该方法具有void返回类型。并且应避免在事件处理程序之外使用async void

如果您想使派生类可以使用异步,请让该方法一开始就返回一个Task

 protected abstract Task HeavyTaskAsync();

如果您需要使用同步覆盖返回一个同步的 Task

override Task HeavyTaskAsync()
{
    // do stuff;
    return Task.CompletedTask;
}

-3
我认为这行代码不支持异步等待:
m_task = Task.Run(() => HeavyTask());

它应该等待什么?没有返回值。

怎么样?

Task.Run(() => HeavyTask()).Wait();

你知道当你调用 Task.Run(x).Wait() 会发生什么吗? 它会将工作转移到另一个线程,但该线程会同步等待。因此,与其让单个线程执行所需的 HeavyTask,你需要一个线程执行 HeavyTask,另一个线程等待它。 - Linky
@Linky,你说的根本就没有意义。我认为wait()只是指示线程在执行I/O操作时等待直到完成工作,因此它是同步的相同线程。 - Arman
1
不完全是这样。Task.Run(() => HeavyTask()).Wait()Task task = Task.Run(() => HeavyTask()); task.Wait();基本相同。Task task = Task.Run(() => HeavyTask())做了什么?它将HeavyTask()转移到另一个线程上。task.Wait()做什么?它同步等待任务。因此,您正在将HeavyTask转移到另一个线程,但是从原始线程等待另一个线程。这有什么好处? - Linky

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