Using one thread to do a specific task multiple times C#

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.

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?

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());
    }
}

Upvotes: 3

Views: 1891

Answers (2)

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149618

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?

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

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:

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.

@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.

Upvotes: 2

Christoph Fink
Christoph Fink

Reputation: 23113

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:

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.

Upvotes: 2

Related Questions