5

Trying to understand async-await in C#, and a bit stuck in a "chicken and egg" problem, maybe.

Does an async method need to call another async for it to be asynchronous?

As a high level example, I'm trying to do a simple write to the file system, but not sure how I can make this task awaitable, if at all.

public Task<FileActionStatus> SaveAsync(path, data)
{
    // Do some stuff, then...

    File.WriteAllBytes(path, data); // <-- Allow this to yield control?

    // ... then return result
}

That line of code is being called within a method that I'm trying to make asynchronous. So while the file is being written, I'd like to yield control to the application, but not quite sure how to do that.

Can someone enlighten me with a very high-level example of how I could write a file to the file system in an async way?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
trnelson
  • 2,715
  • 2
  • 24
  • 40
  • 3
    Just one comment re `Does an async method need to call another async for it to be asynchronous?` - the question is backwards. I have found that IMO the better approach is to first think about the operation (e.g., writing a file), ask if it's naturally asynchronous (i.e., would it block a thread if implemented synchronously), find an appropriate async API (e.g., there is no `WriteAllBytesAsync` but you can make your own), and *lastly* make the calling method `async` so it can call the API using `await`. – Stephen Cleary Nov 21 '15 at 16:42
  • @StephenCleary great advice, and a really good way to think about it going forwards. Thanks! – trnelson Nov 22 '15 at 13:03

2 Answers2

7

Does an async method need to call another async for it to be asynchronous?

Usually that is the case as async goes all the way to the bottom of the call-stack and ends at the lowest place which is actually making the IO operation. In your case, you're using File.WriteAllBytes which is a blocking synchronous call. You can't magically make it asynchronous.

Can someone enlighten me with a very high-level example of how I could write a file to the file system in an async way?

To do that you need to be using a mechanism which exposes an asynchronous API, such as FileStream:

public async Task<FileActionStatus> SaveAsync(string path, byte[] data) 
{
    using (FileStream sourceStream = new FileStream(path,
    FileMode.Append, FileAccess.Write, FileShare.None,
    bufferSize: 4096, useAsync: true))
    {
        await sourceStream.WriteAsync(data, 0, data.Length);
    }
    // return some result.
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Please note that although an asynchronous method should call other asynchronous methods, these other methods can be asynchronous without using the`async` keyword for their implementation. For example, they can be implemented using the .NET 4.0 style by using `Task.Factory.StartNew` and the `ContinueWith` method. You can still use `await` to asynchronously wait for the completion of such asynchronous methods. – Yacoub Massad Nov 21 '15 at 14:40
  • 1
    @YacoubMassad Yes, but using `Task.Run` isn't a naturally async operation. If you use that, you're simply using the async over sync anti-pattern, you're not really using async IO. – Yuval Itzchakov Nov 21 '15 at 14:57
  • @YuvalItzchakov, I agree, but what I meant to say is that you can still have asynchronous methods without really using the `async` keyword. For example, you can have a method that returns a `Task` and that wraps some EAP operation (via `TaskCompletionSource`) without using the `async` keyword. – Yacoub Massad Nov 21 '15 at 20:21
  • @YacoubMassad Well, using `TaskCompletionSource` as a wrapper for the EAP is different than using `Task.Run`. The former will really only wrap a naturally async operation with an awaitable. – Yuval Itzchakov Nov 21 '15 at 20:54
  • @YuvalItzchakov, Yes, but OP is asking whether an `async` method has to call `async` methods only. And I am assuming the OP means the `async` keyword. In this case the answer shouldn't be yes. – Yacoub Massad Nov 21 '15 at 20:58
-1

In addition to Yuval answer: in case if your classes don't have any async methods, you run the method on a background thread by using Task.Run so your code could look like this:

public Task<FileActionStatus> SaveAsync(path, data)
{
  return Task.Run<FileActionStatus>(() =>
    {
      File.WriteAllBytes(path, data);
      // don't forget to return your FileActionStatus
    });
}

Update: threads take resources. Think twice how you are going to use them. Async wrapping of a sync operation could give you benefits at least in UI responsiveness and parallelism. Test different scenarios before choose one. In other cases, use single thread.

svick
  • 236,525
  • 50
  • 385
  • 514
vzayko
  • 337
  • 2
  • 11
  • 2
    That isn't true. You're not producing an async operation using `Task.Run`. You're simply queuing a *synchronous* operation and executing it on a thread-pool thread. You're acutally being counterproductive, as async is meant to free the thread while doing IO, while this does the exact opposite. This is called the [async over sync anti-pattern](http://blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx), and I would discourage anyone from using it. – Yuval Itzchakov Nov 21 '15 at 14:58
  • Probably my answer is too general, I'm sorry. But still. In some cases this approach would give lot of benefits like UI responsiveness. Working with huge files is probably a good reason to move the code to another thread. BTW this one as well as some other benefits are mentioned in the blog you linked above. – vzayko Nov 21 '15 at 15:19
  • @YuvalItzchakov It's an anti-pattern in a library, but it does have its place in UI applications and possibly other situations. Even though truly asynchronous code is more efficient, that efficiency may not be worth the added complexity. – svick Nov 21 '15 at 16:33
  • @svick Of course it has place. But the OP specifically asked how to write *asynchronously* to a file. While this would be a good candidate as an answer to the question "how do I keep my UI responsive during an IO operation only exposed by a synchronous method?" this isn't it. Further more, people do mistakenly look at answers like this and think "ok, this is how you do async". So perhaps if you do pick this is a route, make sure you explain the implications of going that way. – Yuval Itzchakov Nov 21 '15 at 16:37
  • @YuvalItzchakov People who ask questions don't always use the correct terminology. The core of the question seem to be: "while the file is being written, I'd like to yield control to the application", I think this answer does answer that well. (But it's not clear to me why does OP want to use async.) – svick Nov 21 '15 at 16:38
  • 1
    @svick I guess we're interpreting the question differently then :). I generally don't agree with the this answer saying *"you still can **produce it by yourself** by using Task.Run"*. No, you can't produce a naturally async operation by using `Task.Run`, while you *can* use this to keep your UI responsive, if such is in question. Terminology matters. – Yuval Itzchakov Nov 21 '15 at 16:41
  • @YuvalItzchakov Why actually not? Before async/await era, how have you worked with long operations? Have you ran it in the main thread? I hope not. What async/await has changed in this approach? – vzayko Nov 21 '15 at 19:22
  • @vzayko Not sure what you mean by "worked with long operations", as this is about async operations and not long running ones. For asynchronous operations, you had the APM and the EAP patterns. Although they were cumbersome, you still didn't need a threadpool thread for that. – Yuval Itzchakov Nov 21 '15 at 19:35
  • Agree with Yuval. OP asked about doing an I/O-bound operation asynchronously. Pretty safe bet they want to do it in a non-blocking fashion, not move the blocking to the threadpool. when MS took a construct designed for parallelism (Task) and re-purposed it as an async promise, I think it caused a lot of confusion. Whenever someone answers a question about async I/O with "use Task.Run", it only adds to that confusion. It's *almost never* sound advice. – Todd Menier Nov 22 '15 at 14:41