0

I am trying to run multiple tasks in parallel (multithreaded), however, tests are showing they are still running concurrently. The code below seems to be similar to other examples I have read here on SO, along with the MS docs, its probably something I am just missing or not understanding.

Task.Run(async () =>
{
    while (_runSensors)
    {
        var tasks = new List<Task>
        {
            Task.Factory.StartNew(() => { 
                _sensors.Find(item => item.Name == nameof(SensorA)).Refresh(); }, token),
            Task.Factory.StartNew(() => {
                _sensors.Find(item => item.Name == nameof(SensorB)).Refresh(); }, token),
            Task.Factory.StartNew(() => {
                _sensors.Find(item => item.Name == nameof(SensorC)).Refresh(); }, token)
        };
        await Task.WhenAll(tasks);
    }
}

The signature for the 'Refresh()' method that is called is simply:

public void Refresh() { ... }

Where the internal workings are irrelevant to the question, basically reads from an input source and processes it (converts/formats/etc) and updates public properties of the sensor object.

Each of the 'sensors' above takes approximately 15-20 ms (as calculated by Stopwatch). With each sensor added to the list above, the runtime until await Task.WhenAll(tasks); is completed increases 15-20ms. ie. if there are 8 sensors added each iteration is roughly 120-160ms.

What I am trying to do is get them to run in parallel so that say 5 sensors takes approximately the same amount of time per iteration as 3 sensors. I do realize there would be some overhead with thread swapping as those numbers get higher

Edit #1 - Refresh Method from Sensor

public override void Refresh()
{
    Read(Offset, Range);  // Reads a portion of the screen (bitblt copy) ~8ms

    _preprocesed = Preprocess(_buffer); // OpenCV methods to prep for OCR

    var text = _preprocesed.ToText(_ocrOptions); // Tesseract OCR conversion ~10ms

    var vals = text.Trim().Replace(" ", string.Empty).Split('/'); // Reformat

    if (vals.Length != 2)
        return;

    int.TryParse(vals[0], out var current);
    // Setter (only updates if changed INotifyPropertyChanged)
    Current = current; 

    int.TryParse(vals[1], out var refreshCap);
    // Setter (only updates if changed INotifyPropertyChanged)
    RefreshCap = refreshCap;
}
Aaron Murray
  • 1,920
  • 3
  • 22
  • 38
  • 1
    Tasks are (still) not threads and async is not parallel: https://www.wintellect.com/tasks-are-still-not-threads-and-async-is-not-parallel https://code-maze.com/csharp-delegates/ – jazb Nov 02 '18 at 07:14
  • 1
    Not enough information to explain your problems – TheGeneral Nov 02 '18 at 07:20
  • just to reiterate - nothing in this code is guaranteed to run in `parallel`... – jazb Nov 02 '18 at 07:23
  • What sort of application is this? Winforms? WPF? Console? They each handle tasks slightly differently. See [task schedulers](https://msdn.microsoft.com/en-us/ie/dd997402(v=vs.85)#MainContent) – John Wu Nov 02 '18 at 07:24
  • Possible duplicate of [Running async methods in parallel](https://stackoverflow.com/questions/38634376/running-async-methods-in-parallel) – Azrael Nov 02 '18 at 07:24
  • @JohnB so System.Threading.Tasks is probably the wrong approach to what I am trying to accomplish. – Aaron Murray Nov 02 '18 at 07:26
  • In short we have no idea, there is just not enough information here. What ever is causing your results is not evident in your description or your code. – TheGeneral Nov 02 '18 at 07:27
  • @JohnWu this is a part of a library that could be included in any of those (Winforms is what I am currently using for testing, but it could be used in WPF or console apps as well) – Aaron Murray Nov 02 '18 at 07:28
  • @TheGeneral you have said that twice now :) think of it as pseudo code rather than actual code.. The *goal* is that I have to refresh multiple sensors (various input devices), convert the inputs (processing, filtering, etc) to normalized outputs (which the Refresh() method of the sensor does), and I want each Refresh to be called in parallel so that each iteration is updating the values quickly. – Aaron Murray Nov 02 '18 at 07:31
  • Start up 5 endless Threads, Start 5 never ending tasks, use other tpl methods. however because we do not know what the nature of `refresh multiple sensors` actually is, we don't know how to help you. The results you have explained could be pointing to serial access to some resource, or locking, or anything. Anything we say here is just a guess without more information and more results of your testing – TheGeneral Nov 02 '18 at 07:37
  • @TheGeneral I am sorry I thought it was clear in the question. A 'sensor' reads input from some source, (could be say a block of memory read, could be a portion of the screen read, could be mouse events read, it could be network streams read) any kind of input source read into a buffer. Then the refresh method converts it, processes it, filters it and spits out the resulting output (well, sets a property).. Simply put the sensors I have created so far have been taking ~15 to 20ms to execute and they are done. – Aaron Murray Nov 02 '18 at 07:47
  • That said, my 'goal' is to be able to run these refreshes in parallel instead of consecutively so that 5 sensors being refreshed doesn't take 75-100ms, rather takes 15-20ms for all 5 to be refreshed so the iteration can start again. I am not sure how to explain it better, or I am not clear what is confusing. – Aaron Murray Nov 02 '18 at 07:51
  • 1
    @JohnB: Since Stephen's "there is no thread" post everyone tends to go to the other extremity about tasks. In fact, both `Task.Factory.StartNew` and `Task.Run` _do_ start the task on a pool thread if the default scheduler is used. – György Kőszeg Nov 02 '18 at 08:25
  • @AaronMurray The thing is, your current code should already be executing those methods in parallel (how many in parallel is decided by the threadpool, still you shouldn't see 8x longer execution time with 8 tasks). From there, it's tempting to think that there is some kind of synchronization mechanism in your sensor code, hence the need for more information. In short: yes your code should work, if it doesn't then there's something else we're not seeing – Kevin Gosse Nov 02 '18 at 08:30
  • @AaronMurray Please try adding the `TaskCreationOptions.LongRunning` flag to your `Task.Factory.StartNew` calls. If nothing changes, then we can take the threadpool out of the equation. – Kevin Gosse Nov 02 '18 at 08:34
  • @KevinGosse that didn't change anything. I have added code from one of the sensors, others are similar 'style'. So right now its pretty consistent, none of the sensors access anything locked and with 1 sensor, ~55 ips (iterations/sec), 2 sensors ~35 ips, 3 sensors ~ 18-20 ips. – Aaron Murray Nov 02 '18 at 09:09
  • One way to dig further could be to put a breakpoint somewhere inside of the `Refresh` method, then look at the parallel stacks panel of Visual Studio (Debug -> Windows -> Parallel Stack) to see what the other threads are doing. Repeat multiple times. – Kevin Gosse Nov 02 '18 at 09:39
  • As a troubleshooting step, I suggest running two different instances of the program, each accessing one sensor, to see if they interfere with each other. If they do, the problem is not your code, and could be in the hardware interface, for example. – John Wu Nov 02 '18 at 10:12
  • @JohnWo I am running separate and completely different ( classes/implementations) instances, mostly the instances are related to the screen reader, with different preprocessing and formatting, these tend to be the longer running (heavier) sensors, stuff like memory reading tends to be quicker due to quicker read speed, less processing involved which makes them harder to test the above. – Aaron Murray Nov 02 '18 at 14:24
  • @KevinGosse that is fantastic, I have never seen/used that 'parallel stakcks' window. So it definitely appears that they are running on separate threads according to that. Which means everyone has been correct in that the problem does not lie within the code provided, it is in fact working as intended. I am going to have to dig further now to see why as I add sensors it still runs at n*20 ms. – Aaron Murray Nov 02 '18 at 14:37
  • @AaronMurray RE: "I am running separate classes/implementations." That is not what I suggested. For troubleshooting, I suggested running as two *programs*, so that they are separate *processes*, and threading is not at issue. If you have two processes that interfere with one another, it has to be a hardware or hardware driver issue and is not related to your code. – John Wu Nov 05 '18 at 06:17

0 Answers0