Ricker Silva
Ricker Silva

Reputation: 1135

Task.Wait locks the flow of the controller in MVC5

We are consuming a WebAPI from an MVC site. We did a proof of concept from a console application like this, consuming AddUser Method, which reguster a new user in our DB.

static void Main()
    {
        M_Users user = new M_Users();

        var newUser = new M_Users()
        {
            Password = "123",
            FirstName = "Andrew",
            Email = "[email protected]",
            AdminCode = 1,
            IsDisabled = false,
            CountryId = 48,
            CityId = 149,
            DepartmentId = 3,
            CreationDate = DateTime.Now,
            CreatorUserId = 1
        };
        var star = RunAsync(newUser);
        star.Wait();

        //THIS LINE IS REACHED ALWAYS
        Console.WriteLine(star.Result.UserID);
        Console.ReadLine();
    }

    static async Task<M_Users> AddUserAsync(M_Users newUser)
    {
        M_Users user = new M_Users();
        string requestUri = "api/User/AddUser";

        using (var client = new HttpClient())
        {
            client.BaseAddress = new Uri("http://localhost:44873/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // HTTP POST
            HttpResponseMessage response = await client.PostAsJsonAsync<M_Users>(requestUri, newUser);
            if (response.IsSuccessStatusCode)
            {
                Uri newUserUrl = response.Headers.Location;
                user = await response.Content.ReadAsAsync<M_Users>();
            }

            return user;
        }
    }

This AddUserAsync method is called exactly the same in both cases,console application and MVC application, and methods in the biz layer are also the same, and not depicted here for doesn´t seem relevant.

In Console it just worked. User is registered and console printed the id which the user were saved with in the BD. This is pretty much what we needed to proof. That from a sync method we can invoke an async method. Now we try the same from the controler of our site, like this.

    public ActionResult Index()
    {
        User spUser = null;

        var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
        var model = new LoginViewModel();

        // here we invoke sharepoint context for getting user info to bild the object

        M_Users new_user = new M_Users()
                    {
                        Password = "123",
                        FirstName = spUser.Title,
                        Email = spUser.LoginName.Split('|')[2].ToString(),
                        AdminCode = 1,
                        IsDisabled = false,
                        CountryId = 48,
                        CityId = 149,
                        DepartmentId = 3, 
                        CreationDate = DateTime.Now,
                        CreatorUserId = 1
                    };


                    var returnedUser = AddUserAsync(new_user);
                    returnedUser.Wait(); //HERE IT STOPS RUNNING

                    //THIS LINE IS NEVER REACHED
                    ViewBag.UserId = returnedUser.Result.UserID;

                    return View(model);
    }

What needs to be done for the execution to continue.

We also tried ContinueWith() and it runs, but the async method is not executed and nothing is recorded in the DB. More specific, the method is invoked but it seem to go over it and retunrs nothing. :(

Upvotes: 0

Views: 1279

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456457

This is pretty much what we needed to proof. That from a sync method we can invoke an async method.

That's the wrong lesson to learn. Console applications use a different threading framework than ASP.NET, and that's what's tripping you up.

I explain it in full on my blog; the gist of it is that the await captures a "context" and uses that to resume the async method. In a Console app, this "context" is the thread pool context, so the async method continues running on some arbitrary thread pool thread, and doesn't care that you've blocked a thread calling Wait. However, ASP.NET has a request context that only allows in one thread at a time, so the async method is attempting to resume within that request context but it can't because there's already a thread blocked in that context.

The best solution is to allow async to grow through the codebase:

public Task<ActionResult> Index()
{
  User spUser = null;
  var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
  var model = new LoginViewModel();
  M_Users new_user = new M_Users()
  {
    Password = "123",
    FirstName = spUser.Title,
    Email = spUser.LoginName.Split('|')[2].ToString(),
    AdminCode = 1,
    IsDisabled = false,
    CountryId = 48,
    CityId = 149,
    DepartmentId = 3, 
    CreationDate = DateTime.Now,
    CreatorUserId = 1
  };

  var returnedUser = await AddUserAsync(new_user);
  ViewBag.UserId = returnedUser.UserID;
  return View(model);
}

Upvotes: 6

Related Questions