【问题标题】:Question on concurrency/tasks in .net core 3 controller.net core 3 控制器中的并发/任务问题
【发布时间】:2020-01-16 15:06:07
【问题描述】:

我有一个关于 .net core 3 中控制器设置的任务/阻塞/异步的问题。我已将主控制器方法设置为异步,但对如何处理其中的下游异步调用有疑问

我有代码工作,只是想知道它是否以最佳方式完成。

控制器代码(本讨论感兴趣的方法),_verseReader 被注入,并且是一个接口,startup.cs 可以根据配置设置它以从几乎任何存储(xml、sql、mysql、cosmos、mongo)中读取等)...控制器不关心正在使用什么存储。这都是异步的,所以实际的“阅读”是作为一项任务完成的。

...
[HttpGet("{translation}")]
        public async Task<ActionResult<List<VerseSet>>> GetVerses([FromRoute] string Translation, [FromQuery] string Verse, [FromQuery] bool IncludeVerseNumbers=true)
        {
            VerseService vs = new VerseService(_verseReader);
            var backgroundTask = Task.Run(() => vs.GetVerses(Translation, Verse, IncludeVerseNumbers));
            var vList = await backgroundTask;
            return vList == null ? (ActionResult)NotFound(vList) : Ok(vList);
        }
...

vs.GetVerses 不是从控制器设置异步(因为这已经是全部异步)。它调用 ReadVerses,后者进行一些处理、解析和设置:

 public List<VerseSet> GetVerses(string TranslationName, string VerseMatchSpec, bool IncludeVerseNumbers)
        {
            List<VerseSet> vList = new List<VerseSet>();
            ...
            VerseSet v = ReadVerses(TranslationName, s, IncludeVerseNumbers);
            ...
            return vList;
        }

ReadVerses 看起来像:

 private VerseSet ReadVerses(String TranslationName, String VerseMatchSpec, bool IncludeVerseNumbers)
        {
           ...some code here
            List<VerseInfo> theVerseInfo = _verseReader.ReadFromStorage(v.Translation, vs.bookname, vs.chapter, vs.v1, vs.v2);
           ...some code here
            return v;
        }

现在是 ReadFromStorage 方法(注入类的),它实际上调用从配置中设置的任何存储源(Xml、SQL、Mongo 等)获取数据。一切都已同步完成,因为这一切都已由控制器在 Task 中生成。但是,对于 CosmosDB,它必须使用异步调用,所以我不得不这样做(参见对 .Result 的调用!)。我必须这样做,因为 ReadFromStorage 没有被指定为异步的。

 public List<VerseInfo> ReadFromStorage(String Translation, String Bookspec, int Chapter, int StartingVerse, int EndingVerse)
        {
            ...code here
            var bookResult = client.CreateDocumentQuery<CosmosBook>(UriFactory.CreateDocumentCollectionUri(_connInfo["DatabaseId"], "books"), new FeedOptions { EnableCrossPartitionQuery = true , MaxItemCount = 1})
                .Where(b => b.Matchspec.Contains($"[{Bookspec}]"))
                .AsDocumentQuery()
                .ExecuteNextAsync<CosmosBook>().Result;
            ...code here
            return vList;
         }

我已经阅读了 .Result 并且它似乎阻塞(当然)所以我想知道这是否是一个糟糕的设置。这一切都已经在主控制器方法的衍生任务中了。如果我在这里尝试使用 await,我必须使整个调用链异步?!?那是我的问题。据我了解,我必须使 ReadFromStorage 异步,然后 ReadVerses 也异步,然后再进一步 GetVerses 异步。所以我想知道在这种情况下什么是最好的。 .Result 好吗?

还有什么方法可以在不使整个调用堆栈异步的情况下进行等待(这将强制在 4 个不同的时间创建任务)...这看起来很愚蠢(而且很慢)。

没有错误信息,代码按原样工作正常......这是一个关于优化的问题。您必须使所有调用者的(更改代码)异步,只是为了在某个调用堆栈中使用 await down,这似乎很令人困惑?或者,也许我错过了一些东西。谢谢。

【问题讨论】:

  • 我刚刚找到了 Stephen Cleary 的 very good article。希望它会有所帮助
  • 好的,谢谢,我去看看。我想我的一般问题是:您如何处理存在您不想更改(或无法更改)的现有调用堆栈,但您发现现在需要添加(向下 N 个级别)的情况一些突然变成异步的新代码?
  • 通常我会尝试创建此调用堆栈async,但如果我别无选择,只能对异步函数进行同步调用,我可能会使用GetAwaiter().GetResult() 而不是.Result,因为它传播异常而不是将它们包装在AggregateException 中。但是我会提防可能的死锁
  • 这似乎更适合Code Review,因为您的问题似乎符合on-topic list 中的大多数标准。

标签: c# .net .net-core task


【解决方案1】:

我已将所有代码重构为异步,现在一切都变得有意义,并且更加连贯(和正确)。我还在我的类阅读器方法调用中添加了“Async”后缀,以符合约定。

在阅读了 4 小时关于任务和等待/异步的内容后,我更好地理解了“任务”和“线程”之间的区别(它们不是一回事)。来自其他语言的“Task”通常用来表示“Start New Thread”,所以这引起了很多混乱,因为我的印象是在方法上添加 await/async 会为每个调用的方法启动新线程。但事实并非如此。只有在明确调用 Task.Run 时才会启动新线程,并且通常仅用于将计算密集型例程发送到后台 THREAD 以进行处理。也许这对第一次遇到 .net async/await(和 .net 任务)的其他人有所帮助。

[HttpGet("{translation}")]
        public async Task<ActionResult<List<VerseSet>>> GetVerses([FromRoute] string Translation, [FromQuery] string Verse, [FromQuery] bool IncludeVerseNumbers=true)
        {
            VerseService vs = new VerseService(_verseReader);
            List<VerseSet> vList = await vs.GetVerses(Translation, Verse, IncludeVerseNumbers);
            return vList == null ? (ActionResult)NotFound(vList) : Ok(vList);
        }
   public async Task<List<VerseSet>> GetVerses(string TranslationName, string VerseMatchSpec, bool IncludeVerseNumbers)
        {
            ...
            VerseSet v = await ReadVerses(TranslationName, s, IncludeVerseNumbers);
            ...
            return vList;
        }
private async Task<VerseSet> ReadVerses(String TranslationName, String VerseMatchSpec, bool IncludeVerseNumbers)
        {
            ....
            List<VerseInfo> theVerseInfo = await _verseReader.ReadFromStorageAsync(v.Translation, vs.bookname, vs.chapter, vs.v1, vs.v2);
            ...
        }

【讨论】:

    【解决方案2】:

    如果我在这里尝试使用 await,我必须使整个调用链异步?!?这是我的问题。

    是的。你应该去async all the way

    .Result 好吗?

    “没问题”,因为它会工作(即不会导致 YSOD)。但是,在 ASP.NET 上,ResultTask.Runartificially limit your server's scalability

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-10
      • 1970-01-01
      • 1970-01-01
      • 2020-04-19
      • 2014-11-05
      • 2020-02-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多