4

We have a generic Job class which have an abstract HeavyTask method like this:

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

And the derived class override the HeavyTask function and also make it async:

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

Then when we are using this method, it seems that the HeavyTask() is not awaited:

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

Output:

All Done
JobFoo is Done

If we don't have async for the override HeavyTask, then it is working as expected. But I cannot guarantee those whose override the Job won't make the HeavyTask async. I want to understand why it is not awaited successfully and is there a way to make sure it will awaited? If you can, could you also explain whether it is a good practice to override a non-async function as async as shown above?

i3arnon
  • 113,022
  • 33
  • 324
  • 344
Yuchen
  • 30,852
  • 26
  • 164
  • 234
  • 7
    old async void issue. do not use `protected abstract void HeavyTask(); `, use `protected abstract Task HeavyTask(); ` – xZ6a33YaYEfmv Oct 02 '15 at 19:24
  • Why do you have separate start and wait methods? It looks like you should just have a single `StartJob` method which returns a `Task` and have each subclass decide how the task is created. – Lee Oct 02 '15 at 19:24
  • @EhsanSajjad That not the method OP's asking about. – i3arnon Oct 02 '15 at 19:27
  • Possible duplicate of [async/await - when to return a Task vs void?](http://stackoverflow.com/questions/12144077/async-await-when-to-return-a-task-vs-void) – xZ6a33YaYEfmv Oct 02 '15 at 19:34
  • Hello @Lee, thanks for pointing this out. Yes, you are totally right. There is no need to have separate `WaitJob` function here. – Yuchen Oct 02 '15 at 19:41

2 Answers2

11

It's not awaited because there's no awaitable (i.e. Task) to await. That method has a void return type. And you should avoid using async void outside of event handlers.

If you want to enable a derived class to use async have the method return a Task to begin with:

 protected abstract Task HeavyTaskAsync();

And if you then need to have a synchronous override return a Task synchronously:

override Task HeavyTaskAsync()
{
    // do stuff;
    return Task.CompletedTask;
}
i3arnon
  • 113,022
  • 33
  • 324
  • 344
-3

I don't think this line is awaitable:

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

What is it spouse to wait for? No value is returned.

how about

Task.Run(() => HeavyTask()).Wait();
Arman
  • 885
  • 7
  • 9
  • Do you know what happens when you call `Task.Run(x).Wait()` ? It offloads the work to another thread but the thread is synchronously waited for. So instead of a single thread doing the `HeavyTask` you require one thread doing `HeavyTask` and one thread waiting for it. – Linky Oct 05 '15 at 10:07
  • @Linky, what you said doesn't even make sense. I think wait() only instructs the thread to wait until it finishes the work if it does I/O operation, thus it is the same tread making it synchronize. – Arman Oct 06 '15 at 16:46
  • 1
    Not really. `Task.Run(() => HeavyTask()).Wait()` is pretty much the same as `Task task = Task.Run(() => HeavyTask()); task.Wait();` What does `Task task = Task.Run(() => HeavyTask())` do? It offloads HeavyTask() onto another Thread. What does `task.Wait()` do? It waits synchronously for a Task. So you're offloading HeavyTask to another Thread but are waiting for the other thread from the original one. What benefit comes from this? – Linky Oct 07 '15 at 07:13