Which process is best to implement continuous work

2019-06-14 18:31发布

问题:

I want the below process run continuously.But confused to use Thread or Task. I am also new in implementing Thread or Task.Is the process is right I am implementing? Between Thread and Task which is better for getting fast performance for long running process?

private void BtnStart_Click(object sender, EventArgs e)
    {
        IClockThread();

    }

This method is creating thread for every machine.

  public void IClockThread()
    {

        txtStatus.BeginInvoke((Action)(() => txtStatus.Text = "Thread for IClock Starts......" + Environment.NewLine));

        Thread[] ts = new Thread[IclockDetails.Count];
        for (int i = 0; i < 5; i++)
        {
            string IP = IclockDetails[i].IpAddress;
            ts[i] = new Thread(() =>
              {
                  ConnectMachineIClock(IP);
              });
            ts[i].Start();

        }
        txtStatus.BeginInvoke((Action)(() => txtStatus.Text += "Thread for IClock Ends............" + Environment.NewLine));
 }

This is another method which is called by every thread.

    public void ConnectMachineIClock(string IP)
    {
        int idwErrorCode = 0;
        var Iclock = IclockDetails.Where(a => a.IpAddress == IP).FirstOrDefault();

        if (AttnMachineRepository.IsMachineOnline(IP) == true)
        {


            Stopwatch sw = Stopwatch.StartNew();
            blnCon = CZKEM1.Connect_Net(IP.Trim(), 4370);
            sw.Stop();
            if (blnCon == true)
            {
                UpdateText(1, txtStatus, Iclock.IpAddress);
                iMachineNumber = Iclock.Id;
                LocId = Iclock.LocationId;
                MType = Iclock.MachineTypeId;
                LocName = AttnMachineRepository.GetPunchLocation(LocId);
                CZKEM1.RegEvent(iMachineNumber, 65535);
                UpdateText(2, txtStatus, Iclock.IpAddress);
                //txtStatus.BeginInvoke((Action)(() => txtStatus.Text += ("Connected with " + Iclock.IpAddress + " " + sw.Elapsed.TotalSeconds + " Seconds taken to connect") + Environment.NewLine));
                MachineIP = Iclock.IpAddress;
                Get_IClock_LogData(iMachineNumber);

            }
            else
            {
                CZKEM1.GetLastError(ref idwErrorCode);
                UpdateText(-1, txtErrorLog, Iclock.IpAddress);
                //txtErrorLog.BeginInvoke((Action)(() => txtErrorLog.Text += "Unable to connect the device with IP: " + MachineIP + ", ErrorCode = " + idwErrorCode.ToString() + "" + Environment.NewLine));
                //Application.DoEvents();
            }
        }
        else
        {
            UpdateText(-2, txtErrorLog, Iclock.IpAddress);
            //txtErrorLog.BeginInvoke((Action)(() => txtErrorLog.Text += "IP " + MachineIP + " not found" + Environment.NewLine));
            //Application.DoEvents();
        }
    }
      public void UpdateText(int status, TextBox text, string IP)
    {
        switch (status)
        {
            case 1:
                text.BeginInvoke((Action)(() => text.Text += ("Data Processing for" + IP + " starts") + Environment.NewLine));
                Application.DoEvents();
                break;
            case 2:
                text.BeginInvoke((Action)(() => text.Text += ("Connected with " + IP) + Environment.NewLine));
                Application.DoEvents();
                break;
            case -1:
                text.BeginInvoke((Action)(() => text.Text += "Unable to connect the device with IP: " + IP + ", ErrorCode = " + -1 + "" + Environment.NewLine));
                Application.DoEvents();
                break;
            case -2:
                text.BeginInvoke((Action)(() => text.Text += "IP " + IP + " not found" + Environment.NewLine));
                Application.DoEvents();
                break;

        }
    }

回答1:

Analysis

So your current code basically spins up a new thread for each clock detail, in order to basically have those calls all run asynchronously. It doesn't await for any threads to finish.

The first problem with that is creating new threads is expensive, and running 100 tasks on 100 threads is very rarely, if ever, as efficient as say 4 threads running in parallel taking care of tasks as they become available.

So the first issue is that creating a bunch of new threads is not the best approach here.

Secondly, hitting up the network stack with 100 threads (or however many are in IClockDetails) is also less efficient than fewer concurrent threads.

Difference Between Task and Thread

To directly answer your first question, it is better to use Task's in this case. Also the only difference you will get from doing

new Thread(() => {}).Start();

And doing

Task.Run(() => {});

Is that the first way will always create a new thread (expensive) to do the work on, whereas the second will possibly use an existing, idle thread.

Secondly, a Task always creates a background thread, not foreground, so the Task's thread won't keep your application alive if your main and all foreground threads finish, whereas a foreground thread will.

That is it. Your code with the exception of possibly being run on an existing idle thread, and it being run as a background thread, there is no difference at all.

Standard Solution

As mentioned, creating a new thread is expensive. So the first and simple answer to your question is, it is much better to change your new Thread(() => {}).Start(); calls to Task.Run(() => {}); calls.

public void IClockThread()
{
    txtStatus.BeginInvoke((Action)(() => txtStatus.Text = "Thread for IClock Starts......" + Environment.NewLine));

    for (int i = 0; i < IclockDetails.Length; i++)
        Task.Run(() => ConnectMachineIClock(IclockDetails[i].IpAddress));

    txtStatus.BeginInvoke((Action)(() => txtStatus.Text += "Thread for IClock Ends............" + Environment.NewLine));
}

I have cleaned up the code, fixed your for loop to use the actual IclockDetails length, removed unnecessary bits, and replaced the thread with the Task.

This will now re-use threads as they become available, and run them as background threads.

Parallel to the rescue

As mentioned though, if your IclockDetails has 100 items or anything more than say 20, its inefficient to just run threads for each item. Instead you could parallel them up.

Consider this

Parallel.For(0, 100, (i) =>
{
    Console.WriteLine($"I am {i} on thread {Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(10 * i);
});

This will create a bunch of methods which will get called in parallel as the Partitioner sees fit. So this will run based on the most optimal thread count for the system its running on

It will just output to the console the i of which work item it is between the range I specified of 0 to 100. It delays more the larger the number. So if you run this code now you will see a bunch of output like this

I am 0 on thread 1
I am 1 on thread 1
I am 2 on thread 1
I am 5 on thread 3
I am 10 on thread 4
I am 3 on thread 1
I am 15 on thread 5
I am 6 on thread 3
I am 4 on thread 1
I am 20 on thread 6
I am 25 on thread 7
I am 11 on thread 4
I am 35 on thread 11
I am 8 on thread 1
I am 30 on thread 8
I am 45 on thread 12
I am 7 on thread 3
I am 40 on thread 10
I am 50 on thread 9
I am 55 on thread 14
I am 60 on thread 15
I am 16 on thread 13
I am 65 on thread 5
I am 70 on thread 16
I am 75 on thread 18
I am 9 on thread 1
...

As you can see the run order is out of sync as they are run in parallel and the threads are getting re-used.

You can limit the max number of parallel tasks running at once if you like with

Parallel.For(0, 100, new ParallelOptions { MaxDegreeOfParallelism = 4 }...

To limit it to 4 in this example. However, I would personally leave it up to the Paritioner to make that decision.

For clear understanding, if you limit the MaxDegreeOfParallelism = 1 your output would be

I am 0 on thread 1
I am 1 on thread 1
I am 2 on thread 1
I am 3 on thread 1
I am 4 on thread 1
I am 5 on thread 1
I am 6 on thread 1
I am 7 on thread 1
I am 8 on thread 1
I am 9 on thread 1
I am 10 on thread 1
...

NOTE: The Parallel.For call does not finish and carry on to the next line of code until all the work is done. If you want to change that just do Task.Run(() => Parallel.For(...));

Best Solution

So with that in mind, my proposal for your situation and the best solution is to use Parallel.For to split up your work and delegate it onto the right threads and make re-use of those threads as the system see's fit.

Also note I only use Task.Run here to maintain your Thread for clock start/end outputs so they act exactly the same and your IClockThread() method returns before the work is done, so as to not change your current code flow.

public void IClockThread()
{

    txtStatus.BeginInvoke((Action)(() => txtStatus.Text = "Thread for IClock Starts......" + Environment.NewLine));

    Task.Run(() =>
    {
        Parallel.For(0, IclockDetails.Count, (i) =>
        {
            ConnectMachineIClock(IclockDetails[i].IpAddress);
        });
    });

    txtStatus.BeginInvoke((Action)(() => txtStatus.Text += "Thread for IClock Ends............" + Environment.NewLine));
}


回答2:

The main difference between a Task and a Thread is how the concurrency is done.

A Task is concurrency that is delegated to a thread in the application's thread pool. The idea is that a Task is a reasonably short lived, concurrent procedure or function, that is handed off to a thread in the thread pool where it is executed and finished and then the thread being used is returned back to the thread pool for some other task.

See the MSDN documentation Task Class provides an overview with links to various method descriptions, etc.

The Task class represents a single operation that does not return a value and that usually executes asynchronously. Task objects are one of the central components of the task-based asynchronous pattern first introduced in the .NET Framework 4. Because the work performed by a Task object typically executes asynchronously on a thread pool thread rather than synchronously on the main application thread, you can use the Status property, as well as the IsCanceled, IsCompleted, and IsFaulted properties, to determine the state of a task. Most commonly, a lambda expression is used to specify the work that the task is to perform.

For operations that return values, you use the Task < TResult > class.

Also see Microsoft Docs Task-based Asynchronous Programming as well as Task-based Asynchronous Pattern (TAP) in Microsoft Docs which is the current recommended approach (see Asynchronous Programming Patterns for a discussion in Microsoft Docs on several patterns).

This MSDN article Asynchronous programming describes using the async and await keywords with links to additional articles including the Microsoft Docs article Async in depth which gets into the gory details.

A Thread is concurrency that is created by the application. The idea is that a Thread is a reasonably long lived, concurrent procedure or function.

A major difference between the two, Task and Thread, is that a Task is handed off to a ready to run thread while a Thread has to be created and spun up. So the startup time is lower and startup efficiency is higher for a Task as the thread it is delegated to already exists.

In my opinion the primary reason to use a Task is to be able to make a short lived action or procedure concurrent. So things such as accessing a web site or doing some kind of a data transfer or performing a calculation of some kind that requires several seconds or updating a data store are the ideal types of activities for a Task. There are a large number of Async type functions with .NET and C# and C++/CLI that are designed to be used with Task to trigger activities while allowing the UI to remain responsive.

For a Thread, activities such as providing a server that accepts a high volume of requests and acts on them or a function that is monitoring several devices or sensors for a long period would be ideal types of activities for a Thread. Other activities would be additional UI threads in order to handle a complex User Interface or compute intensive tasks such as graphics rendering whose throughput would be enhanced by being able to use one or more dedicated CPU cores.

One point to remember is that Task versus Thread is not a binary, either one or the other decision. One or more Threads may be created to handle particular, encapsulated and self sufficient functionality and within one or more of the Threads created the Task construct may be used to handle the work of a Thread. A common example is the UI thread which is designed to handle the various messages of a User Interface however particular actions that happen within the UI thread are handled by a Task. Another common example would be a server thread which handle multiple concurrent connections using Tasks.

The Microsoft Docs article, The Managed Thread Pool, has this to say:

There are several scenarios in which it is appropriate to create and manage your own threads instead of using thread pool threads:

  • You require a foreground thread.

  • You require a thread to have a particular priority.

  • You have tasks that cause the thread to block for long periods of time. The thread pool has a maximum number of threads, so a large number of blocked thread pool threads might prevent tasks from starting.

  • You need to place threads into a single-threaded apartment. All ThreadPool threads are in the multithreaded apartment.

  • You need to have a stable identity associated with the thread, or to dedicate a thread to a task.

In addition see the following stackoverflow postings and discussions about the differences between Task and Thread.

  • Task vs Thread differences

  • What is the difference between task and thread?