如何在 C# 中运行可变数量的并发参数化无限循环类型的线程?
Posted
技术标签:
【中文标题】如何在 C# 中运行可变数量的并发参数化无限循环类型的线程?【英文标题】:How do you run a variable number of concurrent parametrizable infinite loop type of threads in C#? 【发布时间】:2021-03-16 06:51:50 【问题描述】:我正在创建我的第一个基于多线程 C#/.NET 的应用程序,它将在 Azure Service Fabric 集群上运行。正如标题所说,我希望运行可变数量的并发参数化无限循环类型的线程,这将利用 RunAsync 方法。
每个子线程看起来像这样:
public async Task childThreadCall(...argument list...)
while (true)
try
//long running code
//do something useful here
//sleep for an independently parameterizable period, then wake up and repeat
catch (Exception e)
//Exception Handling
在 RunAsync 方法中调用的此类子线程数量不定。我想做这样的事情:
protected override async Task RunAsync(CancellationToken cancellationToken)
try
for (int i = 0; i < input.length; i++)
ThreadStart ts[i] = new ThreadStart(childThreadCall(...argument list...));
Thread tc[i] = new Thread(ts);
tc[i].Start();
catch (Exception e)
//Exception Handling
所以基本上每个子线程都独立于其他子线程运行,并且永远这样做。有可能做这样的事情吗?有人能指出我正确的方向吗?有什么需要注意的陷阱吗?
【问题讨论】:
除非您有特定的用例,否则我会避开 Thread 类 有多长?我们在谈论什么样的工作,是否受 I/O 限制? @PeterBons 每个独立线程的工作基本上是查询一个 Azure Data Explorer 集群,等待结果,解析结果并将其存储在 Azure Blob Storage 中。然后等待预定义的时间间隔(比如 1-2 小时),然后重新启动此过程。这将永远持续下去,直到将应用部署到 Service Fabric 群集。请注意,每个线程的睡眠时间都是独立设置的。 @PeterBons 请注意,我不需要同步线程完成。一旦正确触发,每个人都可以完成自己的工作。 【参考方案1】:RunAsync
方法在服务启动时被调用。所以是的,它可以用来做你想做的事。我建议使用任务,因为它们可以很好地与取消令牌配合使用。这是一个草稿:
protected override async Task RunAsync(CancellationToken cancellationToken)
var tasks = new List<Task>();
try
for (int i = 0; i < input.length; i++)
tasks.Add(MyTask(cancellationToken, i);
await Task.WhenAll(tasks);
catch (Exception e)
//Exception Handling
public async Task MyTask(CancellationToken cancellationToken, int a)
while (true)
cancellationToken.ThrowIfCancellationRequested();
try
//long running code, if possible check for cancellation using the token
//do something useful here
await SomeUseFullTask(cancellationToken);
//sleep for an independently parameterizable period, then wake up and repeat
await Task.Delay(TimeSpan.FromHours(1), cancellationToken);
catch (Exception e)
//Exception Handling
关于陷阱,有一个很好的list 在使用任务时一般要考虑的事情。
请注意,任务最适合 I/O 密集型工作。如果您可以发布在长期运行过程中究竟做了什么,那么我可以改进答案以最适合您的用例。
尊重传递给 RunAsync 方法的取消令牌的重要一点,因为它表明服务即将停止。它让您有机会优雅地停止工作。来自the docs:
确保传递给 RunAsync(CancellationToken) 的 cancelToken 得到兑现,一旦收到信号,RunAsync(CancellationToken) 就会尽快正常退出。请注意,如果 RunAsync(CancellationToken) 已经完成了它的预期工作,它不需要等待 cancelToken 发出信号并且可以优雅地返回。
正如您在我的代码中看到的那样,我将 CancellationToken
传递给子方法,以便它们可以对可能的取消做出反应。在您的情况下,将因为无限循环而取消。
【讨论】:
请注意,将while (!cancellationToken.IsCancellationRequested)
与await Task.Delay(..., cancellationToken)
结合使用可能会导致取消行为不一致。当令牌被取消时,结果可能是异常的,也可能不是。我的观点是IsCancellationRequested
属性应该很少使用,如果有的话。应该改用ThrowIfCancellationRequested
方法,以确保取消始终是异常的。
@TheodorZoulias 是的,你是对的,最好扔。更新了我的答案。
如果您想确保每个MyTask
实例尽快启动,您可以考虑将await Task.Yield()
放在开头,否则执行将同步运行,直到第一次异步调用被击中。
@DanielLerps await Task.Yield()
仅在没有同步上下文的情况下执行您想要的操作。它这样做是偶然的,因为它是专门为在安装了同步上下文的环境中使用而设计的。出于这个原因,我认为这是一种 hack,我建议改用 Task.Run
方法,这是一种非上下文感知机制,用于将工作卸载到 ThreadPool
。
@PeterBons 谢谢你的回答。它非常接近我的需要。不过,我有一个后续问题,如果说我有 20 个任务,但 SomeUseFullTask() 的并发性限制为 5 个。也就是说,SomeUseFullTask() 对一些有限的计算/IO 资源非常重,因此只有在它可以获取 5 个令牌之一时才需要启动。如果没有令牌,它会等到有令牌可用。有没有可能实现这样的事情?以上是关于如何在 C# 中运行可变数量的并发参数化无限循环类型的线程?的主要内容,如果未能解决你的问题,请参考以下文章