0

I have the following:

void SaveImage(Texture tex, string path)
{
    WritePngAsync(tex, path);
}

public static async void WritePngAsync(Texture tex, string path)
{
    // This code takes a long time to execute
    await PNG.WriteAsync(texture, path);
}

And I call it like so:

for (int i = 0; i < 3; i++)
{
    var imageData = createData();// Create image data somehow
    SaveImage(imageData, $"{i}.png");
}

What I would expect to happen is that the loop would instantly complete because even though WritePngAsync takes a long time SaveImage is not awaited and would return instantly.

I think this is causing issues with the file contents being overwritten when called but this makes me think the code is somehow automatically converted to synchronous code (even though it doesn't seem possible to know when WriteAsync is done).

Does the fact that I omitted await and did not declare SaveImage as async make it safe to use as a synchronous function?

Put another way, does calling an awaited function from a non-async method automatically make the code execute synchronously and return when completed?

Or does the synchronous SaveImage return immediately, before WritePngAsync finishes?

From the comments:

Omitting await does indeed cause the SaveImage() method to execute synchronously. But, that doesn't then propagate to the WritePngAsync() method

So this results in a synchronous that can returns before its function calls finish?

If under the hood PNG.WriteAsync puts texture data into a memory location temporarily before writing, could a successive call then place its data in the same location and thus overwrite the previous image when it is written or would SaveImage being synchronous prevent that?

Startec
  • 12,496
  • 23
  • 93
  • 160
  • No it's not synchronous. If you called a synchronous version of `WraiteAsync`, then it would be. And it would Write one image at a time. – juharr May 01 '21 at 12:05
  • 1
    No, and your "I would expect..." is what would happen, most likely along with a lot of other unwanted behaviour. A telltale sign of something wrong in async land is seeing `async void` on anything but an event handler. – sellotape May 01 '21 at 12:44
  • Omitting `await` does indeed cause the `SaveImage()` method to execute synchronously. But, that doesn't then propagate to the `WritePngAsync()` method called by that method. The latter still does whatever it does, and if that means it writes the file asychronously, it will still do that. See duplicate. – Peter Duniho May 01 '21 at 22:22
  • For your second question (which, if really necessary to ask in the first place, should have been posted as a second question), see https://stackoverflow.com/search?q=%5Bc%23%5D+async+fire+and+forget – Peter Duniho May 01 '21 at 22:23
  • @PeterDuniho I updated the question to reduce the scope. I appreciate the duplicate link but it is asking what if you *do await* a *synchronous* function. What I want to know is what happens if you *do not await* an *asynchronous* function. Hope that is different enough. – Startec May 02 '21 at 00:18
  • _"it is asking what if you do await a synchronous function"_ -- first of all, it's not possible to await a synchronous function. So for sure, that's not what that duplicate is asking. It _is_ asking about the scenario here, but there are other duplicates that you may find more on-point. Lots of people have already asked exactly what you're asking here. I've updated the duplicate targets with another that is perhaps more clearly the same as your scenario. – Peter Duniho May 02 '21 at 01:38

1 Answers1

3

Does the fact that I omitted await and did not declare SaveImage as async make it actually [synchronous]?

No. async void methods are dangerous for several reasons. They're asynchronous, but there's no way for your code to detect when the asynchronous operation completes. Also, since there's no way for your code to detect errors from the asynchronous operation, any exceptions are raised directly on the current context, which usually results in an application shutdown. This behavior seems very odd an undesirable, until you realize that async void methods were specifically designed to be used as event handlers, and should be avoided in other scenarios.

What I would expect to happen is that the loop would instantly complete because even though WritePngAsync takes a long time SaveImage is not awaited and would return instantly.

Yes, the loop completes quickly, and all of the PNG.WriteAsync operations may be incomplete when the loop completes.

I think this is causing issues with the file contents being overwritten when called like this

That sounds reasonable. Your code cannot know when the WritePngAsync methods are complete.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks for the explanation and catching my typo. So essentially, omitting `await` does allow the loop to continue but with undefined completion of the methods that should have been awaited? Should `cs4014` be raised anytime an async method is not awaited? – Startec May 01 '21 at 22:03
  • 1
    Yes, if the code doesn't have an `await`, then the method is not awaited. `CS4014` will catch some scenarios like this; it doesn't catch `async void` because by saying `async void`, the code is explicitly saying that the method isn't awaitable. – Stephen Cleary May 01 '21 at 22:24
  • And "not awaited" means just "fired and forgotten"? Not "it will definitely finish as intended"? – Startec May 02 '21 at 00:03
  • 1
    Yes. "Fire and forget" means it might *not* finish as intended. – Stephen Cleary May 02 '21 at 00:45