【问题标题】:Problems running an async method from a controller's constructor从控制器的构造函数运行异步方法的问题
【发布时间】:2015-11-21 23:12:27
【问题描述】:

我正在做一个项目,我希望用户使用访问令牌/刷新令牌保持登录状态。我将这些值存储在 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() 的呼叫转移到另一个位置,但此时我不知道会在哪里。

【问题讨论】:

  • 你不会使用await LoginModel.SetUserFromAuthenticationCookie();
  • LoginModel.SetUserFromAuthenticationCookie().RunSynchronously();
  • 我无法回答关于异步的问题……但如果您希望此代码在每个操作之前执行,您可以考虑创建一个全局操作过滤器。
  • @JamieD77 我无法从构造函数中等待它,这就是问题所在...... RunSynchronously() 无法完成这项工作。
  • 是的,您仍然需要同步运行它。我只是说动作过滤器可能比构造函数更适合放置它。

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


【解决方案1】:

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

您的构造函数需要如下所示。

public BaseController()
{
    var task = Task.Run(async () => { await LoginModel.SetUserFromAuthenticationCookie(); });
    task.Wait();
}

【讨论】:

  • 我不知道我可以在 Task 的 Run 函数中使用 async/await。感谢您的提醒!我现在遇到的问题是我在某些部分依赖 HttpContext.Current.xxx 并且在新创建的线程中 HttpContext.Current (显然)为空。我已经计划重构我的代码的这些部分,现在将开始着手处理。在我完成并知道您的建议是否解决了我的问题后,我会回到这个问题。感谢您到目前为止的帮助!
猜你喜欢
  • 2018-06-05
  • 1970-01-01
  • 1970-01-01
  • 2014-02-16
  • 2022-11-06
  • 1970-01-01
  • 2020-07-08
  • 2017-05-19
  • 2010-11-03
相关资源
最近更新 更多