-2

Honestly speaking, I've tried so hard to get this to work the way I want, but failed since the await/async and parallel this is so confusing. I've read many questions and online documents and I still mess up. Below is the form and the class. What I'm asking for is this?

  1. When clicking on Start, it turns to Stop.
  2. Two methods from the class run concurrently.
  3. Form to wait for the completions of the concurrent methods.
  4. After completion, Stop button turns to Start.
  5. Label5 says completed.
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace TestingTaskAsync
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Button1_Click(object sender, EventArgs e)
            {
                label8.Text = Environment.CurrentManagedThreadId.ToString();
                Button1.Text = "Stop";
                label5.Text = "";
    
                StartAllCounting();
    
                label5.Text = "Completed.";
                Button1.Text = "Start";
            }
    
            private void StartAllCounting()
            {
                Parallel.Invoke(() => AsyncClass1.StartCount1Async(label3, label6), 
                                () => AsyncClass1.StartCount2Async(label4, label7));
            }
        }
    }


    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace TestingTaskAsync
    {
        internal class AsyncClass1
        {
            public static void StartCount1Async(Label label3, Label label6)
            {
                label6.Invoke((MethodInvoker)delegate () { label6.Text = $"Thread: {Environment.CurrentManagedThreadId}"; });
                for (int xI = 0; xI < 100000; xI++)
                {
                    label3.Invoke((MethodInvoker)delegate () { label3.Text = $"Reached: {xI}"; });
    
                }
            }
    
            public static void StartCount2Async(Label label4, Label label7)
            {
                label7.Invoke((MethodInvoker)delegate () { label7.Text = $"Thread: {Environment.CurrentManagedThreadId}"; });
                for (int xI = 0; xI < 100000; xI++)
                {
                    label4.Invoke((MethodInvoker)delegate () { label4.Text = $"Reached: {xI}"; });
                }
            }
        }
    }

Any light provided, would be highly appreciated.

Sdx.1969
  • 13
  • 1
  • 6
  • Yes it's .Net 6, VS 2022 – Sdx.1969 Mar 18 '23 at 17:00
  • The following may be of interest: [What's the difference between Invoke() and BeginInvoke()](https://stackoverflow.com/questions/229554/whats-the-difference-between-invoke-and-begininvoke) – Tu deschizi eu inchid Mar 18 '23 at 17:08
  • 1
    Yea, sorry for interfering too early. Regarding the methods `StartCount1Async` and `StartCount2Async`, why do the have the `Async` prefix in their name? – Theodor Zoulias Mar 18 '23 at 17:16
  • Because I've tried so many things, and none worked. Yet the Async stuck there. I can edit the question and remove them if necessary. – Sdx.1969 Mar 18 '23 at 17:29
  • I got the difference between Control.Invoke and Control.BeginInvoke. The first would insure the calling thread would wait for the changes to take effect. – Sdx.1969 Mar 18 '23 at 17:45
  • Well, there's nothing asynchronous here. It's not clear whether you're actually trying to change this all and make use of async / await or what else. Take a look at this: [How to download files using HttpClient with a ProgressBar?](https://stackoverflow.com/a/74554087/7444103), might help -- Do not invoke – Jimi Mar 18 '23 at 18:18
  • Yes, it's not clear. Please if anyone take the time to post any answer so I could accept it and close the question. Thanks. – Sdx.1969 Mar 18 '23 at 18:41
  • 1
    ​IMHO removing the `Async` suffix from these non-asynchronous methods would be an improvement. Currently the suffix only serves at creating confusion. – Theodor Zoulias Mar 19 '23 at 06:05

2 Answers2

1

You can just use Task.Run if you only have a couple of methods to run. Parallel is useful if you have much more work to do and need more intelligent partitioning; but with just two operations you can just use Task.Run.

Then, use IProgress<T> for progress reporting. That cleans up the code nicely:

private void Button1_Click(object sender, EventArgs e)
{
  Button1.Text = "Stop";
  label5.Text = "";
    
  await StartAllCountingAsync();
    
  label5.Text = "Completed.";
  Button1.Text = "Start";
}
    
private async Task StartAllCountingAsync()
{
  var progress1 = new Progress<string>(report => label3.Text = report);
  var progress2 = new Progress<string>(report => label4.Text = report);
  await Task.WhenAll(
      Task.Run(() => AsyncClass1.StartCount1(progress1),
      Task.Run(() => AsyncClass1.StartCount2(progress2));
}

public static void StartCount1(IProgress<string>? progress)
{
  for (int xI = 0; xI < 100000; xI++)
  {
    progress?.Report($"Reached: {xI}");
  }
}
    
public static void StartCount2(IProgress<string>? progress)
{
  for (int xI = 0; xI < 100000; xI++)
  {
    progress?.Report($"Reached: {xI}");
  }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Many thanks mate, this example with its simplicity explained a lot. Also I've read your articles regarding async/await. I appreciate all the knowledge you've shared. – Sdx.1969 Mar 25 '23 at 18:10
-4

Try the following to see if it meets your needs - it's adapted from here. See the comments in the code for more information.

If anyone has recommendations for improvement, leave them in the comments below.


Create a Windows Forms App (.NET Framework) (name: ConcurrencyTest)

Create a class (name: ControlExtensions.cs)

Note: This code is from here.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConcurrencyTest
{
    public static class ControlExtensions
    {
        public static void Invoke(this System.Windows.Forms.Control control, System.Action action)
        {
            if (control.InvokeRequired)
                control.Invoke(new System.Windows.Forms.MethodInvoker(action), null);
            else
                action.Invoke();
        }
    }
}

Create a class (name: Class123EventArgsValueUpdated.cs)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConcurrencyTest
{
    public delegate void Class123EventHandlerValueUpdated(object sender, Class123EventArgsValueUpdated e);

    public class Class123EventArgsValueUpdated : System.EventArgs
    {
        public int CurrentManagedThreadId { get; private set; }
        public string Status { get; private set; }

        public Class123EventArgsValueUpdated(int currentManagedThreadId, string status)
        {
            CurrentManagedThreadId = currentManagedThreadId;
            Status = status;
        }
    }
}

Create a class (name: Class123.cs)

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ConcurrencyTest
{
    public class Class123
    {
        //event interested parties can register with to know when value is updated.
        public event Class123EventHandlerValueUpdated ValueUpdated;


        public async Task StartCountAsync(string name, CancellationToken cToken, int delayInMS, int maxVal = 100000)
        {
            for (int i = 0; i < maxVal; i++) 
            {
                if (cToken.IsCancellationRequested)
                    cToken.ThrowIfCancellationRequested();

                System.Diagnostics.Debug.WriteLine($"{name} [{i}]: {i.ToString()}; Environment.CurrentManagedThreadId: {Environment.CurrentManagedThreadId}");

                //if there are subscribers, raise event
                ValueUpdated?.Invoke(this, new Class123EventArgsValueUpdated(Environment.CurrentManagedThreadId, $"Reached: {i}"));

                await Task.Delay(delayInMS);
            }

            //if there are subscribers, raise event
            ValueUpdated?.Invoke(this, new Class123EventArgsValueUpdated(Environment.CurrentManagedThreadId, "Complete"));
        }
    }
}

On Form1 add the following:

  • 4 Label with the following names: labelThreadId1, labelStatus1, labelThreadId2, labelStatus2
  • 1 Button with the following name: btnStartStop

Form1.cs:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ConcurrencyTest
{
    public partial class Form1 : Form
    {
        private CancellationTokenSource _tokenSource = null;
        
        public Form1()
        {
            InitializeComponent();

            //set property
            btnStartStop.BackColor = Color.LimeGreen;
        }

        private async Task StartAllCounting()
        {
            //create new instance
            _tokenSource = new CancellationTokenSource();

            //create reference
            CancellationToken token = _tokenSource.Token;

            //create new instance
            ConcurrentBag<Task> tasks = new ConcurrentBag<Task>();

            //create new instance
            Class123 firstInstance = new Class123();

            //subscribe to event(s)
            firstInstance.ValueUpdated += (s, e) =>
            {
                //update Labels

                if (e.Status != "Complete")
                {
                    labelThreadId1.Invoke(new Action(() => { labelThreadId1.Text = e.CurrentManagedThreadId.ToString(); }));
                    labelStatus1.Invoke(new Action(() => { labelStatus1.Text = e.Status; }));
                }
                else
                {
                    labelThreadId1.Invoke(new Action(() => { labelThreadId1.Text = string.Empty; }));
                    labelStatus1.Invoke(new Action(() => { labelStatus1.Text = "Complete"; }));
                }  
            };

            //add
            tasks.Add(firstInstance.StartCountAsync("firstInstance", token, 25, 2000));

            //create new instance
            Class123 secondInstance = new Class123();

            //subscribe to event(s)
            secondInstance.ValueUpdated += (s, e) =>
            {
                if (e.Status != "Complete")
                {
                    //update Labels
                    labelThreadId2.Invoke(new Action(() => { labelThreadId2.Text = e.CurrentManagedThreadId.ToString(); }));
                    labelStatus2.Invoke(new Action(() => { labelStatus2.Text = e.Status; }));
                }
                else
                {
                    labelThreadId2.Invoke(new Action(() => { labelThreadId2.Text = string.Empty; }));
                    labelStatus2.Invoke(new Action(() => { labelStatus2.Text = "Complete"; }));
                }
            };

            //add
            tasks.Add(secondInstance.StartCountAsync("secondInstance", token, 1, 2000));

            try
            {
                //wait for all tasks to finish
                await Task.WhenAll(tasks.ToArray());
                Debug.WriteLine("All tasks completed.");
            }
            catch (OperationCanceledException ex)
            {
                Debug.WriteLine($"Info (OperationCanceledException) - {ex.Message}");

                //update Labels
                labelThreadId1.Invoke(new Action(() => { labelThreadId1.Text = string.Empty; }));
                labelStatus1.Invoke(new Action(() => { labelStatus1.Text = "Cancelled by user"; }));

                labelThreadId2.Invoke(new Action(() => { labelThreadId2.Text = string.Empty; }));
                labelStatus2.Invoke(new Action(() => { labelStatus2.Text = "Cancelled by user"; }));
            }
            finally
            {
                _tokenSource.Dispose();
            }

            // Display status of all tasks.
            foreach (Task t in tasks)
                System.Diagnostics.Debug.WriteLine("Task {0} status is now {1}", t.Id, t.Status);
        }

        private async void btnStartStop_Click(object sender, EventArgs e)
        {
            //if Button is clicked while the method is executing, request cancellation
            if (btnStartStop.Text == "Stop")
            {
                try
                {
                    if (_tokenSource != null)
                    {
                        _tokenSource.Cancel();
                        Debug.WriteLine("Task cancellation requested.");
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }

                return;
            }    

            //set values
            btnStartStop.Text = "Stop";
            btnStartStop.BackColor = Color.Red;

            await StartAllCounting();

            //set values
            btnStartStop.Text = "Start";
            btnStartStop.BackColor = Color.LimeGreen;
        }
    }
}

Resources:

Tu deschizi eu inchid
  • 4,117
  • 3
  • 13
  • 24
  • 1
    very grateful for your answer, it really helped, and finally I'm able to grasp some info regarding async/await and apply what I learned into the example I posted earlier. It's not an assignment or official task. Just wanted to understand it better. – Sdx.1969 Mar 19 '23 at 02:29