Task.Wait在MVC5中锁定控制器的流程

时间:2014-08-06 23:51:13

标签: c# asp.net-web-api asp.net-mvc-5 async-await

我们正在从MVC网站上使用WebAPI。我们从这样的控制台应用程序中做了一个概念验证,使用了AddUser Method,它在我们的数据库中调节了一个新用户。

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

        var newUser = new M_Users()
        {
            Password = "123",
            FirstName = "Andrew",
            Email = "someAndrew@someplace.com",
            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;
        }
    }

这两个AddUserAsync方法在两种情况下都被称为控制台应用程序和MVC应用程序完全相同,而biz层中的方法也是相同的,这里没有描述似乎不相关。

在控制台中它只是起作用。用户注册并控制打印用户在BD中保存的id。这几乎是我们需要证明的。从同步方法我们可以调用异步方法。现在我们从我们网站的控制器中尝试相同的操作,就像这样。

    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);
    }

要继续执行需要做些什么。

我们还尝试了ContinueWith()并运行,但是不执行异步方法,并且数据库中没有记录任何内容。更具体地说,该方法被调用,但它似乎已经过去并且没有任何重新调整。 :(

1 个答案:

答案 0 :(得分:6)

  

这几乎是我们需要证明的。从同步方法我们可以调用异步方法。

这是错误的教训。控制台应用程序使用与ASP.NET不同的线程框架,这就是绊倒你的原因。

我全面解释on my blog;它的要点是await捕获了一个&#34; context&#34;并使用它来恢复async方法。在控制台应用中,这个&#34;上下文&#34;是线程池上下文,因此async方法继续在某个任意线程池线程上运行,并且不关心您是否阻止了调用Wait的线程。但是,ASP.NET有一个请求上下文,一次只允许在一个线程中,因此async方法试图在该请求上下文中恢复,但它不能,因为它已经是线程在该上下文中被阻止。

最佳解决方案是允许async在代码库中增长:

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);
}