3

I want to do something asynchronously and don't care for the result.
What is the best way to do that?

Public Async Function HandlerForSomeEvent() As Task
  'This is where I do stuff

  'Then this is stuff I want to do without waiting for it
  DoStuff()

  'Here I continue doing other stuff
End Function
Async Sub DoStuff()
  'Doing stuff while the handler continues doing it's stuff
End Sub

'VS

Async Function DoStuff() As Task
  'Doing stuff while the handler continues doing it's stuff
End Function

Everyone tells me to use Function As Task, but as I don't await it I always get the annoying warning in VS.
What is the difference and why should I do it?

Fox
  • 566
  • 4
  • 14
  • 1
    https://stackoverflow.com/q/12144077/11683, https://msdn.microsoft.com/en-us/magazine/jj991977.aspx – GSerg Feb 13 '19 at 14:05
  • `Async Sub` exists only for event handlers. If it's actually an event handler then it should be `Async Sub` but an event handler should also have the appropriate parameters. If it's not an event handler, it should not be `Async Sub`. – jmcilhinney Feb 13 '19 at 14:06
  • @GSerg That's C# – Fox Feb 13 '19 at 14:08
  • 1
    @Fox That does not matter. – GSerg Feb 13 '19 at 14:08
  • @GSerg Are you sure? – Fox Feb 13 '19 at 14:08
  • @jmcilhinney Wdym? What's so different about EventHandlers? The HandlerForSomeEvent() is added via AddHandler, yes. – Fox Feb 13 '19 at 14:09
  • The whole async/await functionality was created with functions in mind. The only reason that `Async Sub` is allowed is because an event handler MUST be a `Sub` and you might want to await an async method in that event handler. It's a necessary evil so don't use it unless you absolutely have to, because that's the way the system was built. – jmcilhinney Feb 13 '19 at 14:19
  • @jmcilhinney Huh? But my event handler is actually a function. – Fox Feb 13 '19 at 14:30
  • Maybe you should actually write code properly then. – jmcilhinney Feb 13 '19 at 14:34
  • @Fox: Are you sure you want fire-and-forget? As in, your code doesn't care whether that code completes, or whether it has an exception? If so, then use `Async Function As Task` and ignore the returned task. In C#, you can assign the returned task to a variable to avoid the warning; not sure if the same would work in VB. – Stephen Cleary Feb 13 '19 at 14:38
  • @jmcilhinney That‘s not my code. And the dev says it depends on what the event is asking for. – Fox Feb 13 '19 at 14:44
  • @StephenCleary Yes. You mean the _ thingy? I saw that, but haven‘t found a vb.net equivalent. – Fox Feb 13 '19 at 14:47
  • @Fox: In C#, it can be any variable name; it doesn't have to be `_`. – Stephen Cleary Feb 13 '19 at 14:52
  • In newer versions of C#, the underscore is specifically a discard, i.e. something used in place of a variable whose value you don't want to use. The value of a discard cannot be used. Before discards were a thing, we just declared a variable but never used its value. You can do the same in VB. – jmcilhinney Feb 13 '19 at 22:28
  • @Fox, if an event is "asking for" a function then it's not properly written code. – jmcilhinney Feb 13 '19 at 22:29

1 Answers1

4

The big reasons for using a Function that returns a Task, rather than an Async Sub are two-fold. The first is exception handling. If you do something like this, you'll get an uncaught exception which will terminate your application:

Public Sub Main()
    Try
        DoSomethingAsync()
    Catch ex As Exception
        Console.WriteLine(ex)
    End Try
End Sub

Private Async Sub DoSomethingAsync()
    Throw New Exception()
End Sub

The exception won't get caught by the Try/Catch block, since it's thrown on a separate thread.

However, if you do this, it will get caught and handled:

Public Async Sub Main()
    Try
        Await DoSomethingAsync()
    Catch ex As Exception
        Console.WriteLine(ex)
    End Try
End Sub

Private Async Function DoSomethingAsync() As Task
    Throw New Exception()
End Sub

The second big reason is because, even though you might not need it now, you might need it later. If it's already a function that returns a task, then you can use it either way you wish. If you don't, you can't await on it, or schedule things to happen after it, or abort it, or do any of the other things that you can do with tasks. So, the question really isn't "why do it?", but rather "why not do it?"

For instance, this outputs "Here" followed by "Done":

Public Sub Main()
    DoSomethingAsync()
    Threading.Thread.Sleep(5000)
    Console.WriteLine("Done")
End Sub

Public Async Function DoSomethingAsync() As Task
    Console.WriteLine("Here")
End Function

As others have mentioned, to avoid the warning, you can assign the task to a variable like this:

Public Sub Main()
    Dim t As Task = DoSomethingAsync()
    Threading.Thread.Sleep(5000)
    Console.WriteLine("Done")
End Sub

It works the same either way and runs on its own thread, just like an Async Sub. It doesn't matter if the Task variable goes out of scope either. The task will continue running even if nothing is referencing it. For instance:

Public Sub Main()
    DoSomething()
    Threading.Thread.Sleep(5000)
    Console.WriteLine("Really Done")
End Sub

Public Sub DoSomething()
    Dim t As Task = DoSomethingAsync()
    Console.WriteLine("Done Doing Something")
End Sub

Public Async Function DoSomethingAsync() As Task
    Console.WriteLine("Here")
End Function
Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
  • 1
    When I await it, my handler stops. That's the point. I want to do both things simultanously. – Fox Feb 13 '19 at 14:14
  • 2
    You don't have to await an async function. Just assign the `Task` it returns to a variable and then you can use that variable later, e.g. test `IsFaulted` to see whether an exception was thrown within it. – jmcilhinney Feb 13 '19 at 14:17
  • @jmcilhinney The problem with that is that I can‘t use long running code in the event handler itself. I musn‘t exceed 3 seconds. The async stuff takes above 10 seconds. And when I assing the tasks to an var, I would still have to await them later, right? – Fox Feb 13 '19 at 14:35
  • @Fox No, if you don't await the task, it still runs on its own, just like the `Async Sub`. Awaiting it is optional. – Steven Doggart Feb 13 '19 at 14:52
  • @jmcilhinney But when I asign it and the handler ends before the task, the var would be disposed, wouldn‘t it? – Fox Feb 13 '19 at 14:56
  • @Fox I updated my answer with a couple more examples. – Steven Doggart Feb 13 '19 at 15:00
  • @StevenDoggart Thanks, that explains everything I wanted to know. :) – Fox Feb 13 '19 at 15:04