【问题标题】:How to seed Users in ASP.NET 5 Startup.cs with Async如何使用异步在 ASP.NET 5 Startup.cs 中为用户播种
【发布时间】:2016-07-17 03:36:28
【问题描述】:

我正在尝试在 ASP.NET 5 应用程序中创建初始“超级用户”。在 MVC 6 / EF7 中使用最新的模板文件。

我可以按照此处列出的示例进行操作:

http://wildermuth.com/2015/3/17/A_Look_at_ASP_NET_5_Part_3_-_EF7

这很好 - 直到我尝试执行异步方法。例如:

await _userManager.CreateAsync(user, "P@55w0rd!");

甚至:

await _context.SaveChangesAsync();

同步方法可以正常工作,并且此代码也在Startup.cs Configure{...} 之外执行。

我在应用程序启动时收到“白屏死机”。我会在没有 async 的情况下完成这一切,但我认为 UserManager 在 Identity 3.0 中没有 Create()。

这是我不懂异步编程还是应该有可能?

编辑:添加了整个调用:

定义一个Seeder 类和一个创建用户的方法:

  public class Seeder
    {
        private ApplicationDbContext _context;
        private UserManager<ApplicationUser> _userManager;

        public Seeder(
            ApplicationDbContext context,
            UserManager<ApplicationUser> userManager)
        {
            _context = context;
            _userManager = userManager;
        }

        public async Task Seed()
        {
            await CreateUsersAsync();
        }

        public async Task CreateUsersAsync()
        {
            var user = await _userManager.FindByEmailAsync("superuser@superuser.com");

            if (user == null)
            {
                var company = _context.Company.First(x => x.Name == "Acme Ltd");

                user = new ApplicationUser
                {
                    UserName = "superuser@superuser.com",
                    Email = "superuser@superuser.com",
                    CreatedDate = DateTime.Now,
                    IsActive = true,
                    CompanyID = company.CompanyId
                };

                await _userManager.CreateAsync(user, "P@55w0rd!!");

            }
        }
    }

配置服务:

public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<Seeder>();
...
}

叫它:

public async void Configure(Seeder seeder)
{
...
await seeder.Seed();
...
}

有趣的是;它确实创建了用户 - 它只是不会继续。所以第二次执行user != null,它执行得很好。

【问题讨论】:

  • 能否将整个方法调用分享给_userManager.CreateAsnyc?控制器代码是什么样的?
  • 我相信 UserManager 中的异步调用会返回一个 IdentityResult 对象,该对象指示该方法是否成功。在该对象中有一个 Result 属性,它包含您的用户。

标签: c# asp.net-mvc entity-framework async-await


【解决方案1】:

有时,您需要实现一个同步接口,但您只有异步 API 可用。没有完美的解决方案。

幸运的是,这段代码只被调用一次,所以性能问题并不重要。您可以通过异步网桥进行脏同步:

public void ConfigureServices(IServiceCollection services)
{
    ConfigureServicesImpl(services).Wait(); //bridge
}

public async Task ConfigureServicesImpl(IServiceCollection services)
{
   await ...;
}

您可能需要插入死锁保护,例如 ConfigureAwait(false) 或 Task.Run(() =&gt; ...).Wait()。

【讨论】:

  • ConfigureServices不能做异步任务吗?还是其他合同的实施部分?还没有开始使用它,所以我肯定错过了其中的一部分。
  • 我假设您正在实现一个接口,否则方法返回类型很重要。在那里你被困住了。异步方法需要 Task 返回类型。 Async void 不起作用,因为调用者将无法等待完成。
  • 根据我下面的评论,我认为Configure 不能返回Task。我的ConfigureServices 也没有直接调用Configure(我的理解是它只是为 DI 目的注册服务?)。 Configure 是从哪里调用的?
  • 我不太了解您的代码结构,因为我看不到您必须实现的接口和约定。这个答案的重要部分是用另一种方法来包装异步实现,该方法执行异步到同步桥。其他模式是可能的。我觉得这很方便,因为您只需桥接一次。
【解决方案2】:

我认为问题是因为签名形成了死锁

public async void Configure(Seeder seeder).

由于它返回 void,等待的任务永远不会返回给创建死锁的调用者。你能做到吗:

public async Task Configure(Seeder seeder).

我还没有开始使用 MVC 6,:( 所以我可能遗漏了一些东西。但这就是出现死锁的原因。

编辑:

由于您无法更改 Configure 的签名,因此请创建一个名为 ConfigureAsync 的方法,该方法返回 Task 类型。现在像往常一样在其中使用您的用户管理器代码等待,并从 Configure 调用 ConfigureAsync 但等待它。

ConfigureAsync.ConfigureAwait(false).Wait()

ConfigureAwait (false) 用于防止等待异步方法完成的潜在死锁。

完整示例:

public void Configure(Seeder seeder)
{
    //Edited due to typo/bad syntax.
    ConfigureAsync(seeder).Wait();
}

public async Task ConfigureAsync(Seeder seeder)
{
   //Now treat this like true async/await.
   await seeder.Seed().ConfigureAwait(false);
}

如果您在这里遇到死锁,也许您的播种机应该是同步的?我会避免在 ASP.NET 上下文中使用 Task.Run(),因为这首先会完全破坏 async/await 的目的,因为这会占用两个请求线程,如果同步完成,这两个线程将在一个线程上完成。

【讨论】:

  • 我认为Configure 不能返回Task。我得到一个“xxx.Startup”类型的“配置”方法的返回类型必须为“Void”。错误。
  • @jidl 我已经更新了解决方案的答案,该解决方案将帮助您从 void 配置同步调用异步。你基本上需要一个异步版本的 configure 并从你原来的 configure 方法同步调用它。
  • 这行得通 - 但.Wait() 不会编译。我把它拿出来用了ConfigureAsync(seeder).ConfigureAwait(false);编辑答案,我会接受它-谢谢!
  • @jidl 我更新了答案,我搞砸了,把 configureAwait(false);在错误的方法调用上。您需要等待 ConfigureAsync(seeder),并在库方法中使用 configureAwait(false) 来帮助避免潜在的死锁。
  • 变量上仍然没有Wait()。它的命名空间是什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-05
  • 1970-01-01
  • 2022-12-09
相关资源
最近更新 更多