8

I'm struggling with how to perform some very-long-running background processing in a world of async methods.

Using the vocabulary from Stephen Cleary's blog, I'm interested in kicking off a "delegate task" after await-ing a "promise task". I want to return the promised value as soon as it's available, and let the delegate task continue on in the background.

To handle exceptions in the un-await-ed delegate task, I'll use an exception-handling continuation modeled after the one described here.

public async Task<int> ComplexProcessAsync()
{
  ...
  int firstResult = await FirstStepAsync();

  // THE COMPILER DOESN'T LIKE THIS
  Task.Run(() => VeryLongSecondStepIDontNeedToWaitFor(firstResult)).LogExceptions();

  return firstResult;
}

Since VeryLongSecondStep...() could take many minutes, and its results are observable purely by side-effect (e.g. records appearing in a database), I reeeaaalllyy don't want to await it.

But as pointed out, the compiler doesn't like this. It warns "Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call."

I could ignore the warning, but I get the feeling I'm not doing this in "correct" way. So what is the correct way?

Thanks!

Community
  • 1
  • 1
David Rubin
  • 1,610
  • 1
  • 17
  • 28

4 Answers4

13

I could ignore the warning, but I get the feeling I'm not doing this in "correct" way. So what is the correct way?

The compiler is warning you that you're not awaiting the task. From my experience, >99% of the time the compiler is correct, the code is wrong because it's missing an await. So this is one of the rare situations where you know what you're doing and want to live dangerously. :)

In this case, you can explicitly assign the task to an unused variable:

var _ = Task.Run(() => VeryLongSecondStepIDontNeedToWaitFor(firstResult));

The compiler is intelligent enough to understand that you're using this variable to silence the "missing await" warning.

On a side note, I do not recommend ContinueWith at all; in the async world, it's just a more dangerous version of await. So I'd write LogExceptions something like this:

public static async Task LogExceptions(this Task task)
{
  try
  {
    await task.ConfigureAwait(false);
  }
  catch (Exception ex)
  {
    LogException(ex);
  }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
3

Since this is within an ASP.NET Web API app I suggest you use HostingEnvironment.QueueBackgroundWorkItem. Stephen Cleary also has a post about it.

        HostingEnvironment.QueueBackgroundWorkItem(ct =>
        {
            try
            {
                // your code
            }
            catch (Exception ex)
            {
                // handle & log exception
            }
        });

https://msdn.microsoft.com/en-us/library/dn636893(v=vs.110).aspx http://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html

William Xifaras
  • 5,212
  • 2
  • 19
  • 21
  • Thank you for the suggestion. What if the application is not an ASP.NET application? Like, say a line-of-business WinForms thing? – David Rubin Nov 20 '15 at 20:50
  • @David Rubin I'm not sure as I'm not a WinForms developer. I'd have to research it. – William Xifaras Nov 20 '15 at 20:51
  • @GianPaolo That's fine, except as noted [here](http://blogs.msdn.com/b/pfxteam/archive/2009/10/06/9903475.aspx), and echoed [here](http://stackoverflow.com/a/9200594/9858), "Task is now the preferred way to queue work to the thread pool." So that's the thrust of the solution I'm looking for - how to queue a background task using the Task system that I won't await on, and do it most effectively. – David Rubin Nov 20 '15 at 21:02
  • Upvoting for the Stephen Cleary link which clearly states "The proper solution is to use a basic distributed architecture." Long-running processes can and will be terminated by ASP.NET's host, even when using QBWI, but, sometimes that's acceptable. – overslacked Nov 20 '15 at 21:11
1

The compiler is trying to protect you from mistakes because it's really easy to forget the await when you actually need to await something, but in this case you really don't need the await, so you can ignore the warning.

However, in this case I think it's a better idea to run the task on some thread from the threadpool, because if you're running it on the main thread like you're doing now, you might cause your application to be non-responsive for new incoming requests/events.

Ilya Kogan
  • 21,995
  • 15
  • 85
  • 141
0

I'd like to suggest you to move VeryLongSecondStepIDontNeedToWaitFor() out of the ComplexProcessAsync() method.

ComplexProcessAsync() itself returns a value, that is being used by at least two tasks (one of them is Task.Run(() => VeryLongSecondStepIDontNeedToWaitFor(firstResult))).

You can combine these tasks via Task.WhenAny():

var result = await ComplexProcessAsync();
await Task.WhenAny(VeryLongSecondStepIDontNeedToWaitFor(firstResult), YourSecondMethod(firstResult))

where YourSecondMethod(firstResult) is the method that uses the ComplexProcessAsync() result.

enkryptor
  • 1,574
  • 1
  • 17
  • 27
  • Thank you for the suggestion, but consider the case that `SecondStep()` is an essential follow-on to `FirstStepAsync()`. That is, `FirstStepAsync()` and `SecondStep()` **must** always occur together, hence why they're wrapped together in `ComplexProcessAsync()`. – David Rubin Nov 20 '15 at 21:14
  • But now they don't. First goes the FirstStep, the program waits for it to complete, then starts the VeryLongSecondStep. – enkryptor Nov 20 '15 at 21:35