用SemaphoreSlim节流-“ Task.Run()”与“ new Func <Task>()”

伊斯特鲁平

这可能并非专门针对SemaphoreSlim,但基本上我的问题是,以下两种限制一组长期运行的任务的方法之间是否存在差异,如果有,差异是什么(以及何时使用其中一种方法) )。

在下面的示例中,假设每个跟踪的任务都涉及从Url加载数据(完全构成示例,但我在SemaphoreSlim示例中发现了一个常见的示例)。

主要区别在于将单个任务添加到跟踪任务列表的方式。在第一个示例中,我们Task.Run()使用lambda调用,而在第二个示例中,我们使用lambda新建了a Func(<Task<Result>>()),然后立即调用该func并将结果添加到跟踪的任务列表中。

例子:

使用Task.Run():

 SemaphoreSlim ss = new SemaphoreSlim(_concurrentTasks);
 List<string> urls = ImportUrlsFromSource();

 List<Task<Result>> trackedTasks = new List<Task<Result>>();
        foreach (var item in urls)
        {
            await ss.WaitAsync().ConfigureAwait(false);
            trackedTasks.Add(Task.Run(async () =>
            {

                try
                {
                    return await ProcessUrl(item);
                }
                catch (Exception e)
                {
                    _log.Error($"logging some stuff");
                    throw;
                }
                finally
                {
                    ss.Release();
                }
            }));
        }
        var results = await Task.WhenAll(trackedTasks);

使用新的Func:

 SemaphoreSlim ss = new SemaphoreSlim(_concurrentTasks);
 List<string> urls = ImportUrlsFromSource();

 List<Task<Result>> trackedTasks = new List<Task<Result>>();
        foreach (var item in urls)
        {
            trackedTasks.Add(new Func<Task<Result>>(async () =>
            {
                await ss.WaitAsync().ConfigureAwait(false);
                try
                {
                    return await ProcessUrl(item);
                }
                catch (Exception e)
                {
                    _log.Error($"logging some stuff");
                    throw;
                }
                finally
                {
                    ss.Release();
                }
            })());
        }
        var results = await Task.WhenAll(trackedTasks);
Theraot

有两个区别:


Task.Run进行错误处理

首先,当您调用lambda时,它将运行。另一方面,Task.Run将其称为。这很重要,因为Task.Run在后台进行了一些工作。它所做的主要工作是处理错误的任务。

如果调用lambda,并且lambda抛出,它将在您将其添加Task到列表之前抛出...

但是,在您的情况下,由于您的lambda是异步的,因此编译器会为其创建Taskfor(您不是手工创建的),并且它将正确处理该异常并通过return使其可用Task因此,这一点尚无定论


Task.Run阻止任务附件

Task.RunDenyChildAttach这意味着在Task.Run运行内部创建的任务与返回的无关(不同步)Task

例如,此代码:

List<Task<int>> trackedTasks = new List<Task<int>>();
var numbers = new int[]{0, 1, 2, 3, 4};
foreach (var item in numbers)
{
    trackedTasks.Add(Task.Run(async () =>
    {
        var x = 0;
        (new Func<Task<int>>(async () =>{x = item; return x;}))().Wait();
        Console.WriteLine(x);
        return x;
    }));
}
var results = await Task.WhenAll(trackedTasks);

将以未知顺序输出从0到4的数字。但是下面的代码:

List<Task<int>> trackedTasks = new List<Task<int>>();
var numbers = new int[]{0, 1, 2, 3, 4};
foreach (var item in numbers)
{
    trackedTasks.Add(new Func<Task<int>>(async () =>
    {
        var x = 0;
        (new Func<Task<int>>(async () =>{x = item; return x;}))().Wait();
        Console.WriteLine(x);
        return x;
    })());
}
var results = await Task.WhenAll(trackedTasks);

每次将按顺序输出从0到4的数字。这很奇怪,对吧?发生的情况是内部任务附加到外部任务,并立即在同一线程中执行。但是,如果使用Task.Run,则内部任务不会附加并独立安排。

即使您使用await这仍然适用,只要您await不执行外部系统任务...

外部系统会怎样?好吧,例如,如果您的任务是从URL读取-如您的示例-系统将创建一个TaskCompletionSource,从中获取Task,设置一个将结果写入的响应处理程序TaskCompletionSource,发出请求,然后返回TaskTask不是计划的,它与父任务在同一线程上运行是没有意义的。因此,它可能会破坏顺序。

由于您await习惯于在外部系统上等待,所以这一点也没有意义


结论

我必须得出结论,这些是等效的。

如果您想安全起见,并确保它能按预期工作,即使在将来的版本中,上述几点不再无聊,也请保留Task.Run另一方面,如果您确实要优化,请使用lambda并避免Task.Run(非常小的)开销。但是,这可能不会成为瓶颈。


附录

当谈论到外部系统的任务时,我指的是在.NET外部运行的内容。可以在.NET中运行一些代码来与外部系统交互,但是大部分代码将不在.NET中运行,因此根本就不会在托管线程中。

API的使用者没有为此指定任何内容。该任务将是一个许诺任务,但这不是暴露的,因为对于消费者而言,没有什么特别的。

实际上,去往外部系统的任务可能根本无法在CPU中运行。此外,它可能只是在等待计算机外部的某个东西(可能是网络或用户输入)。

模式如下:

  1. 图书馆创建一个TaskCompletionSource

  2. 该库设置了一种接收通知的方法。它可以是回调,事件,消息循环,钩子,侦听套接字,管道,等待全局互斥体……等等。

  3. 该库设置代码以对将调用的通知做出反应SetResult,或SetException根据TaskCompletionSource收到的通知进行相应处理。

  4. 该库实际调用外部系统。

  5. 图书馆归还TaskCompletionSource.Task

注意:要特别注意优化,不要在不应该​​优化的地方重新排序,还要注意在设置阶段处理错误。此外,如果CancellationToken是参与,它必须考虑(和呼叫SetCancelledTaskCompletionSource时候appropiate)。另外,对通知的响应(或取消通知)可能会被删除。啊,别忘了验证您的参数。

然后,外部系统开始执行任何操作。然后,当它完成或出现问题时,将通知库,然后您Task的文件突然完成,出现错误……(或者如果发生了取消,则您Task现在被取消了),. NET将根据需要安排任务的继续。

注意:async / await在幕后使用延续,这就是恢复执行的方式。

顺便说一句,如果您想自己实现SempahoreSlim,则必须做与我上面描述的非常相似的事情。您可以在我的SemaphoreSlim移植中看到它


让我们看一些承诺任务的例子...

  1. Task.Delay:当我们等待时Task.Delay,CPU没有旋转。这不是在线程中运行。在这种情况下,通知机制将是OS计时器。当操作系统看到计时器的时间已过时,它将调用CLR,然后CLR将标记任务为完成。什么线程在等待?没有。

  2. FileStream.ReadSync:当我们从存储中读取内容时FileStream.ReadSync,实际工作是由设备完成的。CRL必须声明一个自定义事件,然后将该事件,文件句柄和缓冲区传递给OS ... OS调用设备驱动程序,设备驱动程序与设备连接。随着存储设备恢复信息,它将通过DMA技术写入内存(直接在指定的缓冲区上)。完成后,它将设置一个中断,由驱动程序处理,并通知操作系统,该操作系统调用自定义事件,将该任务标记为已完成。哪个线程从存储中读取了数据?没有。

除了这次设备连接到网络之外,将使用类似的模式从网页下载。如何发出HTTP请求以及系统如何等待响应超出了此答案的范围。

外部系统也可能是另一个程序,在这种情况下它将在线程上运行。但这不是您的进程中的托管线程。


您的收获是这些任务不会在您的任何线程上运行。它们的时机可能取决于外部因素。因此,将它们视为在同一线程中运行,或者我们可以预测它们的时序是没有意义的(当然,对于计时器而言,当然除外)。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

公共静态Task Run(Func <Task>函数);

运行异步lambda时,使用Task.Run(func)还是新的Func <Task>(func)()?

为什么通话不明确?'Task.Run(Action)'和'Task.Run(Func <Task>)'

CS0121 'Task.Run<TResult>(Func<TResult>)' 和 'Task.Run(Func<Task>)' 之间的调用不明确

带有 Func<Task> 或 Func<Task<T>> 参数的同步 void 方法实际上有用吗?

替代Task.Run

Task.FromResult()与Task.Run()

触发并忘记异步Task与Task.Run

如何等待Func <Task <string >>?

如何使用Func <Task <T >>语法?

将lambda异步到Expression <Func <Task >>

从异步lambda动作转换为Func <Task>?

返回using块中间的Func <Task>

如果我使用 new Task(action) -> .Start() 而不是 Task.Run() 创建它,为什么 Task 在调用任何异步方法时停止执行?

_ = Task.Run与异步void | Task.Run与异步子

Task.Run(()MethodName())并等待Task.Run(async()=> MethodName())

要Task.Run还是不去Task.Run

取消 Task.Run 作业

在Task.Run中动态

Task.Run与Invoke()的区别

.ToListAsync()与.ToList()+ Task.Run

如何停止 Task.Run()?

For循环导致Task.Run或Task.Start溢出

Task.Run()是否与创建Task实例,然后与Start()相同?

Parallel.ForEach与Task.Run和Task.WhenAll

Task.Yield(); SyncAction(); vs Task.Run(()=> SyncAction());

使用 Task.Delay 或 Task.Run 启动任务的区别

Task.ContinueWith() 在 Task.Run() 之后不起作用

使用 CancellationToken 与 Task.Run、Task.Wait 混淆