如何在 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# 中运行可变数量的并发参数化无限循环类型的线程?的主要内容,如果未能解决你的问题,请参考以下文章

如何在r中打印可变数量的参数?

如何在java中动态实例化具有不同数量参数的java类?

如何使用可变参数模板参数保存可变数量的参数?

在struts 2中将可变数量的参数从表单传递到动作

如何编写接受无限参数的函数?

jmeter中的参数化