3

I'm trying to work on a proof of concept app, and I would like to know the internals of below two Async Methods.

Would Task.Result cause any issues in DoSomethingAsync1()?

I've read a bit about some blocking and deadlock issues that Task.Result can cause, but I think it wouldn't in this case, since it runs on a separate task and there is an await on the task which already ensures the Result.

The main goal here is to run the async method on a separate task, since this operation doesn't depend on main asp.net thread, and more importantly catch any exceptions thrown by DoSomethingThatTakesReallyLong()

Also,in the ActionResult DoSomething(), should I set .ConfigureAwait(false);?

Are there any hidden bottlenecks/issues that I need to be aware of in the below scenario?

Update

I've fixed the typo I made when typing the question here. (returning user object instead of a task)

Also, I'm not at the liberty of converting all the higher level methods to async at one go in the actual application. So, this is something I'm planning to do in a step-by-step way, starting with operations that doesn't depend on main thread.

I really appreciate all the best-practices answers as well, but this being a little playground code, my main question was to know if there are internal differences between DoSomethingAsync1() and DoSomethingAsync2() that might cause any issues under a certain condition.

From reading through the comments and answers, I get an idea that there isn't much difference.


  private static async Task<User> DoSomethingAsync1()
    {
        try
        {
            var longRunningTask = Task<User>.Factory.StartNew(() => LongRunner.LongRunnerInstance.DoSomethingThatTakesReallyLong());
            var user = await longRunningTask;
            //Will the below line of code cause any issue?
            Console.WriteLine(longRunningTask.Result.Id);
            return user;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            return null;
        }
    }

    private static async Task<User> DoSomethingAsync2()
    {
        try
        {
            var longRunningTask = Task<User>.Factory.StartNew(() => LongRunner.LongRunnerInstance.DoSomethingThatTakesReallyLong());
            var user = await longRunningTask;
            Console.WriteLine(user.Id);
            return user;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            return null;
        }
    }

   public ActionResult Index()
   {

    //Things that run on main Asp.NET thread

    //Things that run independently 
    //Won't be using the task returned here at the moment. 
    DoSomethingAsync1();

    //Do more things on main Asp.NET thread if needed

    return View();             

   }

https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Thanks

Ren
  • 1,493
  • 6
  • 31
  • 56
  • 2
    I wouldn't expect your first code to compile, as you're trying to return a `Task` from an *async* method with a return type of `Task`. A return statement in that method should try to return a `User`. – Jon Skeet Jul 20 '17 at 11:30
  • 1
    also `Task.Factory.StartNew()` is a legacy function that shouldn't ever be used with the async keyword – MikeT Jul 20 '17 at 11:48
  • I must have made a mistake when typing it in here. will update it – Ren Jul 20 '17 at 12:12
  • why did you delete the answer @MikeT I had it upvoted! thanks for some of the clarifications you provided anyway! – Ren Jul 20 '17 at 14:25
  • @Ren because it was getting Trolled because i had simplified some of the details rather than bury you in minute details of how the back end works, and it just isn't worth the aggravation – MikeT Jul 20 '17 at 14:30

2 Answers2

9

Would Task.Result cause any issues [if the task is already completed]?

Task<T>.Result will synchronously wait for the task to complete (the same as task.Wait()), and then return the result (the same as await task).

Since your task is already completed, there is only one other difference between Result and await that is important: Result will wrap exceptions in an AggregateException. For this reason (and also to make the code more refactor-safe), I use await instead of Result whenever possible. That is, take the DoSomethingAsync2 approach.

I've read a bit about some blocking and deadlock issues that Task.Result can cause, but I think it wouldn't in this case, since it runs on a separate task and there is an await on the task which already ensures the Result.

It wouldn't because it's running in the thread pool context.

So, this is something I'm planning to do in a step-by-step way, starting with operations that doesn't depend on main thread.

You may find my brownfield async article helpful. In that article, I refer to this technique as "The Thread Pool Hack".

Are there any hidden bottlenecks/issues that I need to be aware of in the below scenario?

A few:

You shouldn't use StartNew. Use Task.Run instead.

You shouldn't fire-and-forget on ASP.NET (i.e., call an asynchronous method without consuming its task).

You shouldn't expose fake-asynchronous methods (i.e., methods with an asynchronous signature but that are implemented by blocking a thread pool thread). Instead of using Task.Run to implement a method, use Task.Run to call a method.

Combining those three points, the proper structure of The Thread Pool Hack is to use Task.Run with GetAwaiter().GetResult() (which is just like Result but avoids the AggregateException wrapper):

public ActionResult Index()
{
  var r = Task.Run(() => LongRunner.LongRunnerInstance.DoSomethingThatTakesReallyLong())
      .GetAwaiter().GetResult();
  return View();             
}

As a final issue, as long as you have this pattern in your ASP.NET application, the scalability of your application will be decreased. Instead of using one thread during DoSomethingThatTakesReallyLong, your app is now using two; this may also throw off the ASP.NET thread pool heuristics. This may be acceptable during the transition to async, but keep it in mind as a motivator to finish the transition.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thank you very much for giving great insight and clear explanation as always! Sure, I will look into your post about Brownfield Async Development. – Ren Jul 20 '17 at 18:45
2

A normal usage would be something like this:

private static async Task<User> DoSomethingAsync3()
{
    try
    {
        var user = await Task.Run(() => LongRunner.LongRunnerInstance.DoSomethingThatTakesReallyLong());
        Console.WriteLine(user.Id);
        return user;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}

Also, if you were to correctly follow the async all the way pattern, you should change your Index to this:

public async Task<ActionResult> Index()
{
     await DoSomethingAsync3();
     return View();
}
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
  • Re: your second bullet - I'm definitely seeing an `await` in there. Are you not? – Damien_The_Unbeliever Jul 20 '17 at 11:41
  • @Damien_The_Unbeliever Oops, I did not see that at all. Will edit – Camilo Terevinto Jul 20 '17 at 11:42
  • I must have made a mistake when typing it in here. will update that. I mentioned that my above code is a proof of concept. The actual application is much larger and I'm not at the liberty of making all the higher level code async all at once. I'm aware of the best practices a bit, including the fact that Task.Run() should be preferred. It's legacy code, so I wanted to make the PoC code similar. Thanks for the solution on best practices. It'd be great if you can also provide more insight on my question about task.result usage after await. – Ren Jul 20 '17 at 12:40
  • @Ren After await there is no Task.Result since await **gives you** the result if you use it correctly as my code shows – Camilo Terevinto Jul 20 '17 at 12:46
  • I've done the same in my DoSomethingAsync2() as well. My original question was about what happens if Task Result is used after the await. I've got the jist now from provided answers. thanks – Ren Jul 20 '17 at 12:51
  • Can you remove the point where it says my options don't follow the conventions of TPL,as I can't see anything drastically being out of place from usual conventions. As I mentioned in my comment earlier, returning task instead of User was a typo I made when posting the question and I have updated it. – Ren Jul 20 '17 at 14:16
  • 1
    @Ren Answer edited. The most important point is attempting to follow the async all the way pattern as much as possible. You don't have to convert all the code at once, you can do it progressively by making the entry points (the Controller's Action's) async – Camilo Terevinto Jul 20 '17 at 14:21
  • You should not use `ConfigureAwait(false)` on controller action methods because ASP.NET (not ASP.NET Core) has a synchronization context and needs it. – Paulo Morgado Jul 20 '17 at 16:17
  • @PauloMorgado You're right, forgot about that, will edit – Camilo Terevinto Jul 20 '17 at 16:52