2

I need to run some async tasks which result I never gonna use anywhere and I don't care when it will be finished.

For example, I might need my Discord client to respond on some command like this:

// .command
await StartLongAsyncTaskThatMayTakeForeverToCompleteAndSay("I'm late");
await Context.Message.ReplyAsync("Immediately say hi"));
// => "Immediately say hi"
// *few seconds later*
// => "I'm late"

Should I do it with: await StartLongAsyncTask().ConfigureAwait(false); or _ = StartLongAsyncTask(); or should I use Task.Run(() => {} );, and what is the difference?

Arc
  • 43
  • 5
  • You might not care when it finishes but you might care whether it finishes at all or maybe it fails. So worth adding ContinueWith for at least logging purposes. – Evk Jan 13 '23 at 17:33

2 Answers2

2

It hugely depends on what StartLongAsyncTaskThatMayTakeForeverToCompleteAndSay is and the context, for example in some cases assigning invocation result to a task variable and awaiting it after the second call can be ok:

var task = StartLongAsyncTaskThatMayTakeForeverToCompleteAndSay("I'm late");
await Context.Message.ReplyAsync("Immediately say hi"));
await task;

Or just:

await Task.WhenAll(StartLongAsyncTaskThatMayTakeForeverToCompleteAndSay("I'm late"), 
    Context.Message.ReplyAsync("Immediately say hi")));

For differences between the two - see Why should I prefer single 'await Task.WhenAll' over multiple awaits?.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
1

Well you definitely don't want to await your long running task and then reply after, you seem to want to reply right away and let the long running task run its course:

// .command
_ = StartLongAsyncTaskThatMayTakeForeverToCompleteAndSay("I'm late");
await Context.Message.ReplyAsync("Immediately say hi"));
// => "Immediately say hi"
// *few seconds later*
// => "I'm late"

and what is the difference

The difference between the last 2 options on your list (the first one I should think is obvious) is that the Task.Run version runs the async function on the thread pool, which you should never manually do for any sort of well behaved API, while simply calling the task runs it (or at least starts it) on your own thread (which is perfectly fine for I/O bound operations).

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • Thanks. That's exactly what I wanted to know. – Arc Jan 13 '23 at 17:30
  • >`the first one I should think is obvious` I'm still not sure. Could you please explain how it's different from `_ =` ? Sorry if it's kind of stupid question... – Arc Jan 13 '23 at 17:31
  • It's different because you await it, so anything after it only happens *after* the function is completed. It's in the name, *await* for this function to finish then do the next one then the next one and so on. – Blindy Jan 13 '23 at 17:32
  • Oh, then, maybe, I missunderstood how `ConfigureAwait(false)` works. Doesn't it make await.. *not awaited*? – Arc Jan 13 '23 at 17:36
  • 1
    No, it avoids moving the execution context back to the original context. Say your async function awaits a `Task.Run` created task inside, that makes the execution context (think "thread", if you want a gross simplification) from your own to the thread pool's execution context. After the inner task is done and you return back to your own you have two choices: return to the thread you know and love (`ConfigureAwait(true)`) or say you don't care if you continue on whatever you're on now (`ConfigureAwait(false)`). – Blindy Jan 13 '23 at 17:41
  • 1
    In other words, `ConfigureAwait` is an optimization you can use as long as you understand and handle the risks of continuing on some random thread you have no knowledge about. – Blindy Jan 13 '23 at 17:43