从控制器的构造函数运行异步方法的问题

时间:2015-08-27 14:06:44

标签: asp.net-mvc asynchronous async-await

我正在开展一个项目,我想让用户使用访问令牌/刷新令牌登录。我将这些值存储在cookie中,每当用户访问该站点时,无论用于访问该站点的页面如何,我都希望自动登录。为此,我创建了一个BaseController,所有其他控制器都继承自。 BaseController看起来像这样:

public abstract class BaseController : Controller
{
    public BaseController()
    {
        LoginModel.SetUserFromAuthenticationCookie();
    }
}

每次执行操作之前都会执行此构造函数,因此正是我想要的。问题是SetUserFromAuthenticationCookie()是异步方法,因为它必须调用其他异步方法。它看起来像这样:

public async static Task SetUserFromAuthenticationCookie()
    {
        // Check if the authentication cookie is set and the User is null
        if (AuthenticationRepository != null && User == null)
        {
            Api api = new Api();

            // If a new authentication cookie was successfully created
            if (await AuthenticationRepository.CreateNewAuthenticationCookieAsync())
            {
                var response = await api.Request(HttpMethod.Get, "api/user/mycredentials");

                if(response.IsSuccessStatusCode)
                {
                    User = api.serializer.Deserialize<UserViewModel>(await response.Content.ReadAsStringAsync());
                }
            }
        }
    }

问题是执行顺序不是我预期的,因此用户没有登录。我尝试使用.Result处理异步方法,但这导致了死锁。除此之外,我在SO上阅读了很多有关该问题的线索,并最终找到了一个能够使登录工作的线程:How would I run an async Task<T> method synchronously?。虽然它有点hacky并与这个帮手一起工作:

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        synch.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        T ret = default(T);
        synch.Post(async _ =>
        {
            try
            {
                ret = await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();
        SynchronizationContext.SetSynchronizationContext(oldContext);
        return ret;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private bool done;
        public Exception InnerException { get; set; }
        readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple<SendOrPostCallback, object> task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }

如果我然后将BaseController构造函数的内容更改为:

AsyncHelpers.RunSync(() => LoginModel.SetUserFromAuthenticationCookie());

功能按预期工作。

我想知道你是否对如何以更好的方式做到这一点有任何建议。也许我应该将呼叫转移到SetUserFromAuthenticationCookie()到另一个地方,但此时我不知道那会是什么。

1 个答案:

答案 0 :(得分:7)

我在另一个堆栈上找到了这个解决方案。 Synchronously waiting for an async operation, and why does Wait() freeze the program here

你的构造函数需要看起来像这样。

1 ?- partition([1,2,3,4],L,R).
L = [1, 4],
R = [2, 3] ;
L = [2, 3],
R = [1, 4] ;
false.