方法的异步和同步版本

时间:2019-01-18 20:41:31

标签: c# .net async-await task-parallel-library webclient

好吧,所以我已经读了很多书,并且在研究使用async methodstask等的最佳方法。我相信我(大部分是)理解它,但是我想检查一下确定。

我确实开始使用async wrappers为同步任务制作Task.Run(),但是最近读到最好拥有一个sync method,让应用程序确定是否需要将其推送到另一个通过使用Task.Run()本身创建线程,这很有意义。但是,我也已经读到,自然是async functions的例外。我不确定我是否完全理解“自然异步”,但作为基本理解,似乎提供async methods的.NET框架方法是naturally async,而WebClient.DownloadFile/DownlodFileAsync就是其中之一。 >

所以我有两种方法,如果有人愿意提供反馈,我想检验我的理解并确保它是正确的。

方法一是在本地操作系统上移动一些文件,看起来像这样(伪代码):

Public static void MoveStagingToCache()
{
    ...do some file move, copy, delete operations...
}

第二个看起来像这样(伪代码):

Public static void DownloadToCache()
{
    ...do some analysis to get download locations...
    using (var wc = new WebClient())
    {
        wc.DownloadFile(new Uri(content.Url), targetPath);
    }
    ...do other stuff...
}

所以我的理解如下。第一个方法应该保留不变,因为没有一个文件操作方法具有async versions,因此自然不可能async。由调用者决定是在调用MoveStagingToCache()来运行同步还是在调用Task.Run(()=>MoveStagingToCache())来将其推送到后台线程之间。

但是,在第二种方法中,下载自然是async(除非我误会了),因此它可以创建同步和异步版本。为此,我不应该只包装如下的sync方法:

Public static Task DownloadToCacheAsync()
{
    return Task.Run(()=>DownloadToCache());
}

相反,我应该使核心方法异步如下:

Public static async Task DownloadToCache()
{
    ...do some analysis to get download locations...
    using (var wc = new WebClient())
    {
        await wc.DownloadFileTaskAsync(new Uri(content.Url), targetPath);
    }
    ...do other stuff...
}

然后我可以这样创建同步版本:

Public static void DownloadToCache()
{
    DownloadToCacheAsync().Wait();
}

这允许使用自然异步方法,还为需要它的用户提供同步重载。

是对系统的很好理解还是我搞砸了?

顺便说一句,WebClient.DownloadFileAsyncWebClient.DownloadFileTaskAsync的区别仅仅是任务返回用于捕获错误的任务吗?

编辑

好的,所以在评论和答案中进行了一些讨论之后,我意识到我应该在系统和预期用途上添加更多详细信息。该库位于旨在在桌面而不是ASP上运行的库中。因此,我不关心为请求处理保存线程,主要关注的是保持UI线程开放并响应用户,并将“后台”任务推送到系统可以在用户处理时处理的另一个线程去做他们需要做的事情。

对于MoveStagingToCache,这将在程序启动时被调用,但是我不需要等待它完成就可以继续加载或使用程序。在大多数情况下,它将在程序的其余部分启动并运行之前完成,并允许用户执行任何可能的事情(最大运行时间为5-10秒),但是即使在用户交互开始之前未完成,程序也可以正常工作。因此,我的主要愿望是将该操作移出UI线程,开始运行,然后继续执行程序。

因此,对于这一点,我目前的理解是该方法应该在库中同步,但是当我从主(UI)线程调用它时,我只是使用

Task.Run(()=>MoveStagingToCache());

既然完成后我真的不需要做任何事情,那么我真的不需要等待吗?如果仅执行上述操作,它将在后台线程上启动操作吗?

对于DownloadToCache,相似但有所不同。我希望用户能够启动下载操作,然后继续在UI中工作,直到下载完成。完成后,我将需要执行一些简单的操作来通知用户它已准备就绪,并启用“使用它”按钮等。在这种情况下,我的理解是,我将其创建为一个等待方法的异步方法。 WebClient个下载电话。这样会将其从UI线程中移出,以进行实际下载,但是一旦下载完成,它将返回,以允许我在等待调用后执行需要进行的UI更新。

正确吗?

2 个答案:

答案 0 :(得分:8)

shouldn't write asynchronous wrappers for synchronous methods,但您也shouldn't write synchronous wrappers for asynchronous methods-这都是 反模式。

提示:“自然异步” 主要表示基于I / O,但有一些例外。不幸的是,其中之一是某些文件系统操作,其中包括移动文件,这些文件应该是异步的,但API不支持异步,因此我们必须假装它是同步的。

就您而言,DownloadToCache当然是异步的。在这种情况下,我更喜欢公开一个异步API,仅

如果您还必须(或确实要想要)支持同步API,建议您使用boolean argument hack。语义是,如果您传递sync:true,则返回的任务已经完成。这使您可以将逻辑保持在一个方法中,并编写非常小的包装程序,而不会出现通常与那些包装程序相关的陷阱:

private static async Task DownloadToCacheAsync(bool sync)
{
  ...do some analysis to get download locations...
  using (var wc = new WebClient())
  {
    if (sync)
      wc.DownloadFile(new Uri(content.Url), targetPath);
    else
      await wc.DownloadFileTaskAsync(new Uri(content.Url), targetPath);
  }
  ...do other stuff...
}
public static Task DownloadToCacheAsync() => DownloadToTaskAsync(sync: false);
public static void DownloadToCache() => DownloadToTaskAsync(sync: true).GetAwaiter().GetResult();

答案 1 :(得分:3)

您已经问过多个相互交织的问题,让我尝试回答其中大部分问题,首先请读Stephen Cleary的阅读文章,以更清楚地了解Async的工作原理,尤其是关于Naturally Async - There's no thread的工作,这将有助于理解自然的异步方法的含义。

  

异步操作

Async operations IO bound and CPU bound有两种

  

IO绑定异步如何工作

IO绑定是指离开进程边界以调用外部服务(数据库,Web Service,Rest API)的操作,对于这些服务,同步调用导致阻塞线程,等待空闲的进程外调用返回,但在进行异步操作的情况下,可以释放调用线程以处理其他调用。

  

IO异步呼叫返回会发生什么

当调用返回时,可以分配任何工作线程来完成调用,可以使用ConfigureAwait(false)上的Task object来指示。凭借IO绑定异步设计,系统可以实现很高的可伸缩性,因为在每个进程中作为宝贵/有限资源的线程不会在空闲等待中被浪费。

  

真实异步/自然异步/ IO异步

True Async请求可以处理数百万个请求,而同步系统只有100个,底层技术在Windows中称为IO completion ports。它不需要软件线程即可处理请求,它使用基于硬件的并发性

  

CPU绑定异步

另一方面,对于CPU绑定异步用例是在WPF或类似的胖客户端的情况下,它们可以使用Task.Run在单独的线程上进行后台处理,从而释放Ui线程,从而使系统保持大量运行状态响应速度更快,在这里您无需保存线程,因为使用Task.Run调用了工作线程,它的工作过程完全相同,但是系统响应速度更快,这是通过异步处理实现的重要成就。

  

我确实开始使用Task.Run()为同步任务制作异步包装器,但是最近读到,最好有一个同步方法,让应用程序确定是否需要将其推入另一个线程,使用Task.Run()

应用程序没有自动的方法来决定它是您的代码还是正在调用的将创建工作线程的代码。对于与IO绑定的异步,您需要调用正确的API,这将在内部利用Windows(IO完成端口)公开的异步管道以使其纯Async

  

我不确定我是否完全理解“自然异步”,但作为基本理解,似乎提供异步方法的.NET框架方法自然是异步的,而WebClient.DownloadFile / DownlodFileAsync就是其中之一。 strong>

大多数提供异步版本的.Net方法都是IO绑定调用,需要使用Task.Run显式创建CPU绑定,然后等待。对于基于事件的异步实现,TaskCompletionSource是一个不错的选择,它返回正在进行的等待状态Task,可以是SetResultSetException,用于设置事件的结果或错误,从而异步完成TaskTaskCompletionSource也是无线程的实现

  

关于您发布的不同方法

您对Method1的理解是正确的,用户可以决定是否使用后台线程来调用它,因为它释放了主调用线程(可以是Ui线程)。关于Method2,正如注释中所提到的,请确保它一直到顶部都是Async(入口点),因为这是释放要在其他操作中使用的调用线程的唯一方法

  

最后

此代码是一个非常糟糕的主意,在@LexLi的注释中也提到过

Public static Task DownloadToCacheAsync()
{
    return Task.Run(()=>DownloadToCache());
}

Public static void DownloadToCache()
{
    DownloadToCacheAsync().Wait();
}

点对:

  1. 在IO绑定同步方法上执行Task.Run不会有任何优势,因为工作线程始终被阻塞,因此线程与Async方法不同,正在等待空闲,因为线程被阻塞,因此系统可伸缩性没有任何提高< / li>
  2. 如果某个方法的版本为Async,那么它肯定会具有Sync版本,只需简单地使用DownloadToCache()。同步先于异步,我不知道只有异步调用而没有同步的情况
  3. 在调用线程中执行显式Task.Wait(),这会使系统无响应,甚至在少数情况下会由于主线程被阻塞而导致死锁,而其他操作则需要在主/上执行调用线程
  4. 使用Task.Wait()的唯一原因是在后台/工作线程/线程池线程上处理一些逻辑,这些逻辑需要等待,并且可能在进一步的逻辑处理之前使用结果,主要用于CPU绑定操作。这不适用于IO绑定操作

我希望这有助于提供有关Async-Await API用法的一些信息