OwenP
OwenP

Reputation: 25378

Is it just plain wrong to return Task instead of Task<T>?

The task API feels sort of like a pit of failure, given how every possible thing I could do with it comes with more DON'T than DO. But I'm trying to use it more, and I'm confused about the entire issue of async void.

My problem happens in a place where my application's used a local data file forever, and because it was fast synchronous calls were used. Now some remote sources are an option, and they have enough latency I'm switching it all to be asynchronous.

So I've got a method that maybe looks like this:

void InitializeData() {
    SomeProperty = _dataSource.Load(...);
}

The naive thing I want to do for low-friction is:

Task InitializeDataAsync() {
    return Task.Run(() => {
        SomeProperty = _dataSource.Load(...);
    });
}

But there are SO many articles about how terrible it is to use async void, and they seem to blur with discussion of returning a Task. So is my best option really to write it like:

Task<DataType> FetchDataAsync() {
    return _dataSource.LoadAsync(...);
}

...then hunt down all the callers and make them comply?

I feel like the argument people make is that async void is bad because of the different behavior of async Task and the dreaded UnobservedTaskException when you use await. In this situation, I'm very unlikely to ever be using await. Or if I do use await, it's always going to have a try/catch around it because we're already paranoid about UnobservedTaskException.

So:

Upvotes: 1

Views: 398

Answers (2)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249506

async void is not the same as returning a Task. The problem with async void is there is no way to observe task exceptions (other then the global exception handler), and no way to wait for the task to complete (you actually can't await an async void method because there is nothing to await), this is why it should be avoided.

Returning Task does not have these problems, if you await it you know when it completes and you will get exceptions where you await so returning a Task is perfectly acceptable.

Upvotes: 10

Erik Philips
Erik Philips

Reputation: 54618

It looks like you're just confused. The await/async API is incredibly simple. The only confusing part is understanding the difference between await/async API and the Task API. I won't go into great detail about this as the Stephen Cleary's blog clearly covers this, but here is the important excerpt:

To reiterate a sentence from my last post, use Task.Run to call CPU-bound methods (from GUI threads). Do not use it just to “provide something awaitable for my async method to use”.

next...

The naive thing I want to do for low-friction is:

Task InitializeDataAsync() {
  return Task.Run(() => {
    SomeProperty = _dataSource.Load(...);
  });
}

This is a clear case of offloading Sync code into a Async wrapper which doesn't really do any good.

But there are SO many articles about how terrible it is to use async void,

This is clearly not a async void method. This is a non-async Task returning method. Here is an async void method:

async void InitializeDataAsync() {
  await Task.Run(() => {
    SomeProperty = _dataSource.Load(...);
  });
}

they seem to blur with discussion of returning a Task.

I've not read anything that blurs this information. It's pretty simple; if you have an async method, always return Task or Task<T> (except as stated below). The difference between using these two (Task or Task<T>) is purely design/architecture.

So is my best option really to write it like:

Best is really an opinion here. Both of these work and don't forget async/wait

async Task<DataType> FetchDataAsync() {
  return await _dataSource.LoadAsync(...);
}

async Task FetchDataAsync() {
  _data = await _dataSource.LoadAsync(...);
}

I feel like the argument people make is that async void is bad because of the different behavior of async Task and the dreaded UnobservedTaskException when you use await.

The most important difference is how exceptions are handled:

Excerpt:

Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started.

So

Or if I do use await, it's always going to have a try/catch around it because we're already paranoid about UnobservedTaskException.

This means that you can't catch an exception by try/catch around async void. That is a huge problem!

Is "Don't use async void" the same thing as "Don't use Task?"

Well this is apples and oranges. Yes: don't use async void unless you're using them for async event handlers.

Don't use Task's unless they are CPU bound or you're using a Framework call (or you've correctly wrapped your own method which uses IO Completion Ports).

If you don't use await, does it mitigate that?

Hopefully by now you understand that await/async is different from a Task, and what you're trying to do is write all await/async code.

If you do use await, does always wrapping it with a try/catch mitigate the issue?

Depends, as stated above.

Upvotes: 2

Related Questions