【问题标题】:Using Async Graph API in Sync methods在 Sync 方法中使用 Async Graph API
【发布时间】:2020-05-24 20:03:56
【问题描述】:

我是这个异步世界的新手所以请耐心等待我的知识。

目前;我们正在努力实现 Graph API 以获取邮件文件夹、消息等交换数据。由于 Graph API 遵循 APM(异步编程模型),我们遇到了“Sync over Asyc”的情况。

_GraphServiceClient.Me.MailFolders.Request.GetAsync()

背景

让我把情况说一下;邮件发送和不同的邮件相关代码位于我们的根类库中,它具有同步方法。

这些方法将从同一个库的不同类(即执行工作流步骤将发送邮件等)和 ASP.NET Web 应用程序、Windows 窗体应用程序、控制台应用程序中调用。现在,我们不能将邮件发送类的方法标记为异步,因为异步病毒会传播,我们不能让所有调用者都是异步的。将有大量代码需要重构。

从过去几天开始;我一直在阅读 (Stephen Toub & Stephen Cleary”) 的相关见解全文以及一些 SO 帖子。

所以,在阅读了上面的相关文章和网络上的其他文章之后;我认为我需要使用以下方法。 引入 Asynchelper 包装器,它在线程池的另一个线程(使用 Task.Run)中执行 Async 方法并阻塞当前线程。

GraphService 类:使所有的图通信并得到相关结果。

static class GraphService
{
    public async static Task<List<MailFolder>> GetMailFolders(GraphServiceClient graphClient)
    {
        UserMailFoldersCollectionPage mFolders = await graphClient.Me.MailFolders.Request.GetAsync();
        return mFolders.CurrentPage;
    }
}

AsyncHelper

using System;
using System.Threading.Tasks;

internal static class AsyncHelper
{
    // Private ReadOnly _myTaskFactory As TaskFactory = New TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.[Default])

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        // Dim cultureUi = CultureInfo.CurrentUICulture
        // Dim culture = CultureInfo.CurrentCulture
        // Return _myTaskFactory.StartNew(Function()
        // Thread.CurrentThread.CurrentCulture = culture
        // Thread.CurrentThread.CurrentUICulture = cultureUi
        // Return func()
        // End Function).Unwrap().GetAwaiter().GetResult()
        return Task.Run(() =>
        {
            return func();
        }).ConfigureAwait(false).GetAwaiter().GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        // Dim cultureUi = CultureInfo.CurrentUICulture
        // Dim culture = CultureInfo.CurrentCulture
        // _myTaskFactory.StartNew(Function()
        // Thread.CurrentThread.CurrentCulture = culture
        // Thread.CurrentThread.CurrentUICulture = cultureUi
        // Return func()
        // End Function).Unwrap().GetAwaiter().GetResult()
        Task.Run(() =>
        {
            return func();
        }).ConfigureAwait(false).GetAwaiter().GetResult();
    }
}

在根类库中的使用:

List<MailFolder> mFolders = AsyncHelper.RunSync(async () => await GraphService.GetMailFolders(_GraphServiceClient));

您能帮我解决这个架构设计问题吗?

  • 当您别无选择时;哪个是用于“同步异步”和“异步同步”的最佳方法?
  • 我认为“Async”和“Await”运算符不会导致创建额外的线程;那么异步是如何在同一个线程中发生的。技术上; “Async”和“Await”如何在不创建新线程的情况下实现异步行为?实际上;我对 Task.Run 和 Async await 有点困惑。
  • 在 GraphService 类中,如上所述;它只返回当前页面数据。我们需要获取所有邮件文件夹,所以我想我们需要执行循环并再次执行相同的请求以获取所有邮件文件夹所以我认为;如果我们需要遵循该方法,我们可能需要遵循该类中的 Asynchelper.RunSync 选项。

提前致谢。请提出建议。

【问题讨论】:

    标签: c# vb.net multithreading asynchronous async-await


    【解决方案1】:

    对你们中的一些问题的评论:

    当你别无选择时;这是使用的最佳方法 “Sync over Async”和“Async over Sync”?

    如果你想从异步到同步,你需要等待,从而阻塞一个线程。我认为您的 AsyncHelper 没问题,但我看不出您为什么要创建和额外的任务等待。只需等待 Func 给出的任务。而ConfigureAwait(false) 仅在与await 一起使用时才有效。见下文。

    我认为“Async”和“Await”运算符不会导致额外的线程 被创建;那么异步是如何在同一个线程中发生的。 技术上; “Async”和“Await”如何实现异步行为 出去创建一个新线程?实际上;我有点困惑 Task.Run 和异步等待。

    我相信文档说不保证会创建新线程。它可能会创建新线程。它还取决于Context

    在 GraphService 类中,如上所述;它只返回当前页面 数据。我们需要获取所有邮件文件夹,所以我想我们需要 执行循环并再次执行相同的请求以获取所有邮件 文件夹所以我想;我们可能需要遵循 Asynchelper.RunSync 选项 如果我们需要遵循该方法,请在该课程中。

    请注意,异步/等待并不意味着多任务。您可以查看 TPL(任务并行库)。

    AsyncHelper:

    internal static class AsyncHelper
    {
        public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        {
            return func().GetAwaiter().GetResult();
        }
    
        public static void RunSync(Func<Task> func)
        {
            func().Wait();
        }
    }
    

    【讨论】:

    • "但是我看不出你为什么要创建和等待额外的任务。只需等待 Func 给出的任务。并且 ConfigureAwait(false) 仅在与 await 一起使用时才有效。" --- 我没有得到你的上述评论。请您详细说明一下。
    • 您建议的代码更改会导致死锁。当前 UI 线程将在异步操作到来时阻塞,并且当异步完成时,它会等待当前 UI 线程可用。这不会以任何一种方式发生。所以,我们必须包裹在 Task.Run 中以将当前 UI 线程从异步操作中分离出来。
    • 那么不要在这里执行 async/await:AsyncHelper.RunSync(() =&gt; GraphService.GetMailFolders(_GraphServiceClient)); 如果您在异步链中的某个位置使用 async/await,则需要确保您不会通过以下方式返回 UI 线程上下文使用ConfigureAwait(false)。我可能在你的代码中遗漏了一些东西,所以你可能是对的,但它看起来是错误的。而且你仍然阻塞了 UI 线程,不管你包装了多少任务。
    【解决方案2】:

    让我把情况说一下;邮件发送和不同邮件相关的代码位于我们的根类库中,它具有同步方法。现在,我们不能将邮件发送类的方法标记为异步,因为异步病毒会传播,我们不能让所有调用者都是异步的。将有大量代码需要重构。

    当你别无选择时;哪种方法最适合用于“Sync over Async”和“Async over Sync”?

    非常清楚:您可以避免同步而不是异步;你只是不想想要。这个决定没有错;重构为async 可能很重要,应该与其他问题保持平衡,例如向客户实际交付价值。但要认识到这是一个决定,并且任何同步异步解决方案都存在不可避免的缺点。

    当你别无选择时;哪种方法最适合用于“Sync over Async”和“Async over Sync”?

    没有“最佳选择”,否则每个人都只会使用它,而围绕这个问题不会有任何困难或讨论。每种选择都有不同的优缺点,并没有适用于任何地方的解决方案。

    如果可以从线程池线程调用 Graph API,那么您拥有的线程池 hack 应该没问题。只是一个小提示:你方法中的ConfigureAwait(false) 什么都不做,since there is no await to configure

    我认为“Async”和“Await”运算符不会导致创建额外的线程;那么异步是如何在同一个线程中发生的。技术上; “Async”和“Await”如何在不创建新线程的情况下实现异步行为?实际上;我对 Task.Run 和 Async await 有点困惑。

    一开始肯定会让人困惑。 async/await 的核心思想是使用更少个线程。这对于许多操作(例如 I/O)都是可能的,因为the thread can be used for something else while the operation is actually happening

    【讨论】:

    • 当你说“任何同步异步的不可避免的缺点”;考虑到我的上述情况,您想到的这些缺点是什么?我知道要利用最好的异步行为,我们必须通过标记“async-await”来遵循异步原则。然而;有时;就像我说的;我们别无选择;我们不能重写很多代码。因此,考虑到上述情况,您是否认为上述通过异步包装器同步将是比“async-await”更好的解决方案。
    • 感谢您对“On Graph API to run on Thread pool”的意见;我明白了你只在线程池上获取图形数据的观点,一旦获取了图形数据,它最终会释放线程。我会尝试这种方法。然而;它是否涉及上下文切换的开销,是否会是调用应用程序或调用类方法的主要成本?
    • what could be those drawbacks comes in your mind considering my above situation? 一个不可避免的缺点是代码是同步的。其他缺点取决于所使用的确切黑客类型。对于线程池 hack(如您提出的解决方案),缺点是当从线程池线程调用时代码必须正常工作,以及一些较小的性能损失:上下文切换和线程池上的额外压力。
    • 谢谢@Stephen Cleary。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-14
    • 1970-01-01
    • 1970-01-01
    • 2018-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多