使用一个线程多次执行特定任务C#

时间:2022-12-09 02:14:24

I have a class that works with a device, to avoid delays in UI layer when working with device and serial port I used a thread, but my device is limited to do only one job at a time. So, I have a queue that when user asks to do something tasks got added to it, then I run a thread to do tasks one by one.

我有一个与设备一起工作的类,以避免在使用设备和串口时我使用线程的UI层延迟,但我的设备一次只能做一个作业。所以,我有一个队列,当用户要求执行添加任务的任务时,我会运行一个线程来逐个执行任务。

Every time user asks for a task I check if the thread is running or not, if yes I just add new task to queue, if not I create thread again. Which means each time queue got empty, I should create a new thread. Now I want to ask is there any way to reuse the thread? Since I just need one thread to do tasks is it a good idea to use threadpool? Since suspend is an obsolete method and I don't know when the user would ask for another task to use wait(), can I suspend thread and run it again in any other way? or It is best to create thread again and I'm doing it right?

每次用户请求任务时我都会检查线程是否正在运行,如果是,我只是将新任务添加到队列中,如果不是,我再次创建线程。这意味着每个队列都空了,我应该创建一个新线程。现在我想问有没有办法重用线程?因为我只需要一个线程来完成任务,所以使用threadpool是个好主意吗?由于suspend是一个过时的方法,我不知道用户何时会要求另一个任务使用wait(),我可以暂停线程并以任何其他方式再次运行它吗?或者最好再次创建线程,我做得对吗?

public class Modem
{
    Thread thread;
    Queue<Task> Tasks = new Queue<Task>();
    public Modem()
    {
    }

    public void DoTask(Task s)
    {
        Tasks.Enqueue(s);
        if (thread == null || !thread.IsAlive)
           thread = new Thread(HandleTasks);
    }

    private void HandleTasks()
    {
        while (Tasks.Count != 0)
            SendTaskToModem(Tasks.Dequeue());
    }
}

2 个解决方案

#1


2  

is there any way to reuse the thread? Since I just need one thread to do tasks is it a good idea to use threadpool?

有没有办法重用线程?因为我只需要一个线程来完成任务,所以使用threadpool是个好主意吗?

Yes, using the ThreadPool and relying on the framework is usually a better idea then spinning up your own custom implementation.

是的,使用ThreadPool并依赖于框架通常是一个更好的主意,然后启动自己的自定义实现。

What you can do is use one background thread which is responsible for processing your items while another is responsible for queuing them. This is a simple producer-consumer problem. For that, you can use a BlockingCollection:

你可以做的是使用一个后台线程负责处理你的项目而另一个负责排队。这是一个简单的生产者 - 消费者问题。为此,您可以使用BlockingCollection:

public class Modem
{
    private BlockingCollection<Task> _tasks;
    public Modem()
    {
       _tasks = new BlockingCollection<Task>();
    }

    public void QueueTask(Task s)
    {
        _tasks.Add(s);
    }

    private Task StartHandleTasks()
    {
         return Task.Run(async () =>
         {
             while (!_tasks.IsCompleted)
             {
                 if (_tasks.Count == 0)
                 {
                     await Task.Delay(100).ConfigureAwait(false);
                     continue;
                 }

                 Task task;
                 _tasks.TryTake(out task);

                 if (task != null)
                 {
                    // Process item.
                 }
             }
         });
    }
}

This is a rather simplistic example of using a BlockingCollection. It has more built-in features such as using a CancellationToken to cancel an item currently being processed. Note you will have to add proper exception handling, cancellation, etc.

这是使用BlockingCollection的一个相当简单的示例。它具有更多内置功能,例如使用CancellationToken取消当前正在处理的项目。请注意,您必须添加适当的异常处理,取消等。

@ChrFin implementation using TPL Dataflow saves you some overhead in starting up the processing of the tasks and spinning the background thread while there aren't any items to process. I posted this answer to give you another way of solving your problem.

使用TPL Dataflow的@ChrFin实现可以节省您在启动任务处理和旋转后台线程时的一些开销,同时没有任何要处理的项目。我发布了这个答案给你另一种解决问题的方法。

#2


2  

There is something built-in for such tasks: ActionBlock (not distributed with the core framework, you need to add the Microsoft.Tpl.Dataflow NuGet package).
If you create it like:

这些任务内置了一些东西:ActionBlock(不与核心框架一起分发,需要添加Microsoft.Tpl.Dataflow NuGet包)。如果您创建它:

var actionBlock = new ActionBlock<TASK>(t => DoWork(t), 
    new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = 1 });

only one item will be processed at once.

一次只能处理一个项目。

Then you simply call:

然后你只需致电:

actionBlock.Post(yourTask);

and all "posted" tasks are executed one after each other in their own thread.

并且所有“已发布”的任务在他们自己的线程中一个接一个地执行。

When you are done you can call actionBlock.Complete(); and then e.g. await actionBlock.Completion;. There you can also check for exceptions which happend inside DoWork.

完成后,您可以调用actionBlock.Complete();然后例如等待actionBlock.Completion;。在那里,您还可以检查DoWork中发生的异常。

#1


2  

is there any way to reuse the thread? Since I just need one thread to do tasks is it a good idea to use threadpool?

有没有办法重用线程?因为我只需要一个线程来完成任务,所以使用threadpool是个好主意吗?

Yes, using the ThreadPool and relying on the framework is usually a better idea then spinning up your own custom implementation.

是的,使用ThreadPool并依赖于框架通常是一个更好的主意,然后启动自己的自定义实现。

What you can do is use one background thread which is responsible for processing your items while another is responsible for queuing them. This is a simple producer-consumer problem. For that, you can use a BlockingCollection:

你可以做的是使用一个后台线程负责处理你的项目而另一个负责排队。这是一个简单的生产者 - 消费者问题。为此,您可以使用BlockingCollection:

public class Modem
{
    private BlockingCollection<Task> _tasks;
    public Modem()
    {
       _tasks = new BlockingCollection<Task>();
    }

    public void QueueTask(Task s)
    {
        _tasks.Add(s);
    }

    private Task StartHandleTasks()
    {
         return Task.Run(async () =>
         {
             while (!_tasks.IsCompleted)
             {
                 if (_tasks.Count == 0)
                 {
                     await Task.Delay(100).ConfigureAwait(false);
                     continue;
                 }

                 Task task;
                 _tasks.TryTake(out task);

                 if (task != null)
                 {
                    // Process item.
                 }
             }
         });
    }
}

This is a rather simplistic example of using a BlockingCollection. It has more built-in features such as using a CancellationToken to cancel an item currently being processed. Note you will have to add proper exception handling, cancellation, etc.

这是使用BlockingCollection的一个相当简单的示例。它具有更多内置功能,例如使用CancellationToken取消当前正在处理的项目。请注意,您必须添加适当的异常处理,取消等。

@ChrFin implementation using TPL Dataflow saves you some overhead in starting up the processing of the tasks and spinning the background thread while there aren't any items to process. I posted this answer to give you another way of solving your problem.

使用TPL Dataflow的@ChrFin实现可以节省您在启动任务处理和旋转后台线程时的一些开销,同时没有任何要处理的项目。我发布了这个答案给你另一种解决问题的方法。

#2


2  

There is something built-in for such tasks: ActionBlock (not distributed with the core framework, you need to add the Microsoft.Tpl.Dataflow NuGet package).
If you create it like:

这些任务内置了一些东西:ActionBlock(不与核心框架一起分发,需要添加Microsoft.Tpl.Dataflow NuGet包)。如果您创建它:

var actionBlock = new ActionBlock<TASK>(t => DoWork(t), 
    new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = 1 });

only one item will be processed at once.

一次只能处理一个项目。

Then you simply call:

然后你只需致电:

actionBlock.Post(yourTask);

and all "posted" tasks are executed one after each other in their own thread.

并且所有“已发布”的任务在他们自己的线程中一个接一个地执行。

When you are done you can call actionBlock.Complete(); and then e.g. await actionBlock.Completion;. There you can also check for exceptions which happend inside DoWork.

完成后,您可以调用actionBlock.Complete();然后例如等待actionBlock.Completion;。在那里,您还可以检查DoWork中发生的异常。