0

I am new to async and parallel programming and my question revolves around attempting to run a method that takes a couple parameters, and then execute that method in parallel. I need to run this in parallel because the method being called is updating PLC's on my factory floor. If it is down synchrounsly, this process can take nearly 10 minutes because of how many there are. I am able to get the method to run once, using the last PLC in my custom class, but won't run for the other PLC's in my list. Sample code below:

List<Task> task = new List<Task>();
foreach(PLC plc in PlcCollection)
{
    string plcName = plc.Name;
    string tagFormat = plc.TagFormat
    tasks.Add(Task.Run(async () => await MakeTags(plcName, tagFormat)));
}

Parallel.ForEach(tasks, task => task.Start());

// Code to be done after tasks are complete

public async Task MakeTags(string plcName, string tagFormat)
{
    //Code to update the PLC's
}

The method makeTags works like it should when called - it updates the PLC correctly. But it only runs once, and only for one of my PLC values. I have also tried Task.WhenAll(tasks) instead of the Parallel.ForEach, with the same problem. Does anyone have any advice and feedback on what I am doing wrong and anything that I could try? Thanks!

EDIT: To clarify - I am expecting the MakeTags method to be executed in parallel for however many PLC's there are in the PlcCollection (the number will be variable). What I am getting is that the MakeTags method is only being called once, for one PLC element, when there are multiple PLC's in the collection. This process is started from a web app and then the server takes care of the rest. No UI elements are being updated or changed. It's all behind the scenes work.

EDIT 2: See below for a screenshot of the Debugger. In this example 2 PLC's (TTC_WALL and RR_HEPA_EE) are loaded into my list of tasks. However, only the RR_HEPA_EE is processed and then the program completes without an exception being thrown.

enter image description here

Edit 3: I made changes and now code looks like the following:

List<Task> task = new List<Task>();
foreach(PLC plc in PlcCollection)
{
    //plcName and tagFormat are just strings
    tasks.Add(MakeTags(plcName, tagFormat);
}

Task.WhenAll(tasks).ContinueWith(t =>
{
    //Code to do
    Console.WriteLine("****PLC Update Complete*****");
});

return; //program ends

public async Task MakeTags(string plcName, string tagFormat)
{
await Task.Run(() =>
{
    Console.WriteLine("Updating PLC " + plcName);
    //Code to update the PLC's
Console.WriteLine("PLC Updated for :" + plcName);
});
return;
}

However I am still getting only one of the tasks to run. Debugger screenshot below, showing only 1 of the 2 PLC's were updated. I should be seeing a console line saying "PLC Updated for TTC_WALL" as well. enter image description here

12
  • 1
    Do you want to call the MakeTags for all the PLC's at once, or you want to limit the concurrency of the asynchronous operations? Commented Jun 10, 2021 at 17:59
  • 1
    Why are you using Task.Run()? This call queues a worker thread on the threadpool, which is not necessary since MakeTags() is asynchronous and is presumably IO-bound. All you need to do is add MakeTags(plcName, tagFormat) to your Task list then await the result using Task.WhenAll(). Additionally, you don't need to use Parallel.ForEach to "start" the work. When you invoke Task.Run(), a thread is queued to the threadpool and will be run as allowed by your task scheduler. In the case of invoking async methods, they begin when invoked. Commented Jun 10, 2021 at 17:59
  • 1
    @PatrickTucci I am using Task.Run() as that was my understanding on how to set up a task - This is my first ever attempt at async programming and it's been a challenge to learn. If I understand what you are saying, I should try the following: tasks.Add(MakeTags(plcName, tagFormat) then Task.WhenAll(tasks.ToArray());? Commented Jun 10, 2021 at 18:13
  • 1
    @JohnMuraski a Task just represents an asynchronous operation. It is not shorthand for a thread or parallelism. The distinction is important and one that you should research, but outside the scope of comment discussion. To answer your question, yes, the code you posted should do what you want. It's worth noting that if MakeTags() contains CPU-intensive code, it could place a significant load on the server. If it's IO-bound, this should not be an issue. Commented Jun 10, 2021 at 18:31
  • 1
    Most likely the problem is within MakeTags. Commented Jun 10, 2021 at 18:48

1 Answer 1

1

The problem is probably that your code does not wait for the completion of the tasks. You can wait for all of them to complete by using the blocking Task.WaitAll method.

Task[] tasks = PlcCollection
    .Select(plc => MakeTagsAsync(plc.Name, plc.TagFormat))
    .ToArray();
Task.WaitAll(tasks);
Console.WriteLine("****PLC Update Complete*****");

This way the current thread will be blocked until all the tasks have completed, and any exceptions that may have occurred will be surfaced bundled in an AggregateException.

The above approach assumes that the MakeTagsAsync method is genuinely asynchronous. If it's not, and instead it fakes asynchrony by wrapping internally some synchronous code in Task.Run, at first you should read why this is a bad idea here: Should I expose asynchronous wrappers for synchronous methods? Then make the method synchronous again by removing the Task.Run wrapper and changing the return type to void, and use the Parallel class or the PLINQ library to invoke the method in parallel. Here is a PLINQ example:

PlcCollection
    .AsParallel()
    .ForAll(plc => MakeTags(plc.Name, plc.TagFormat));
Console.WriteLine("****PLC Update Complete*****");
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you Theodor Zoulias and @Patrick Tucci! I used the PLINQ method in the answer above and it worked. My method started to run in parallel! Still got a lot to learn for parallel programming but this was a great start.
@JohnMuraski in case you want to control the degree of parallelism, the PLINQ includes a WithDegreeOfParallelism operator that you can attach after the AsParallel operator. Be aware that the parallelism is further limited by the availability of ThreadPool threads. When the ThreadPool is saturated, it injects new threads at a slow rate. So keep in mind the option of using the ThreadPool.SetMinThreads method at the start of the program, to configure (increase) the minimum number of threads that the ThreadPool creates instantly on demand.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.