Sök…


Anmärkningar

En tråd är en del av ett program som kan köras oberoende av andra delar. Den kan utföra uppgifter samtidigt med andra trådar. Multithreading är en funktion som gör det möjligt för program att utföra samtidig behandling så att mer än en operation kan göras åt gången.

Du kan till exempel använda trådar för att uppdatera en timer eller räknare i bakgrunden samtidigt som du utför andra uppgifter i förgrunden.

Multitrådade applikationer är mer lyhörda för användarinmatning och är också lätt skalbara, eftersom utvecklaren kan lägga till trådar när och när arbetsbelastningen ökar.

Som standard har ett C # -program en tråd - huvudprogramtråden. Sekundära trådar kan emellertid skapas och användas för att köra kod parallellt med primärtråden. Sådana trådar kallas arbetartrådar.

För att styra driften av en tråd delegerar CLR en funktion till operativsystemet känt som gängschemaläggare. En trådplanerare säkerställer att alla trådar har tilldelats korrekt körningstid. Den kontrollerar också att trådarna som är blockerade eller låsta inte förbrukar mycket av CPU-tiden.

.NET Framework System.Threading namespace gör det lättare att använda trådar. System.Treading möjliggör multithreading genom att tillhandahålla ett antal klasser och gränssnitt. Bortsett från att tillhandahålla typer och klasser för en viss tråd, definierar den också typer för att hålla en samling trådar, timerklass och så vidare. Det ger dess stöd genom att tillåta synkroniserad åtkomst till delad data.

Thread är huvudklassen i System.Threading namnutrymmet. Andra klasser inkluderar AutoResetEvent , Interlocked , Monitor , Mutex och ThreadPool .

Några av delegaterna som finns i System.Threading namnområdet inkluderar ThreadStart , TimerCallback och WaitCallback .

Enumerations i System.Threading namnutrymme inkluderar ThreadPriority , ThreadState och EventResetMode .

I .NET Framework 4 och senare versioner görs flertrådad programmering enklare och enklare genom System.Threading.Tasks.Parallel och System.Threading.Tasks.Task klasser, Parallel LINQ (PLINQ), nya samtidiga samlingsklasser i System.Collections.Concurrent namnutrymme och en ny arbetsbaserad programmeringsmodell.

Enkel komplett gängdemonstration

class Program
{
    static void Main(string[] args)
    {
        // Create 2 thread objects.  We're using delegates because we need to pass 
        // parameters to the threads.  
        var thread1 = new Thread(new ThreadStart(() => PerformAction(1)));
        var thread2 = new Thread(new ThreadStart(() => PerformAction(2)));

        // Start the threads running 
        thread1.Start();
        // NB: as soon as the above line kicks off the thread, the next line starts; 
        // even if thread1 is still processing.
        thread2.Start();

        // Wait for thread1 to complete before continuing
        thread1.Join();
        // Wait for thread2 to complete before continuing
        thread2.Join();

        Console.WriteLine("Done");
        Console.ReadKey();
    }

    // Simple method to help demonstrate the threads running in parallel.
    static void PerformAction(int id)
    {
        var rnd = new Random(id);
        for (int i = 0; i < 100; i++)
        {
            Console.WriteLine("Thread: {0}: {1}", id, i);
            Thread.Sleep(rnd.Next(0, 1000));
        }
    }
}

Enkel fullständig tråddemonstration med uppgifter

class Program
{
    static void Main(string[] args)
    {
        // Run 2 Tasks.  
        var task1 = Task.Run(() => PerformAction(1)));
        var task2 = Task.Run(() => PerformAction(2)));

        // Wait (i.e. block this thread) until both Tasks are complete.
        Task.WaitAll(new [] { task1, task2 });
        
        Console.WriteLine("Done");
        Console.ReadKey();
    }

    // Simple method to help demonstrate the threads running in parallel.
    static void PerformAction(int id)
    {
        var rnd = new Random(id);
        for (int i = 0; i < 100; i++)
        {
            Console.WriteLine("Task: {0}: {1}", id, i);
            Thread.Sleep(rnd.Next(0, 1000));
        }
    }
}

Explicit Task Parallism

    private static void explicitTaskParallism()
    {
        Thread.CurrentThread.Name = "Main";

        // Create a task and supply a user delegate by using a lambda expression. 
        Task taskA = new Task(() => Console.WriteLine($"Hello from task {nameof(taskA)}."));
        Task taskB = new Task(() => Console.WriteLine($"Hello from task {nameof(taskB)}."));

        // Start the task.
        taskA.Start();
        taskB.Start();

        // Output a message from the calling thread.
        Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name);
        taskA.Wait();
        taskB.Wait();
        Console.Read();
    }

Implicit Task Parallelism

    private static void Main(string[] args)
    {
        var a = new A();
        var b = new B();
        //implicit task parallelism
        Parallel.Invoke(
            () => a.DoSomeWork(),
            () => b.DoSomeOtherWork()
            );

      }

Skapa och starta en andra tråd

Om du gör flera långa beräkningar kan du köra dem samtidigt på olika trådar på din dator. För att göra detta skapar vi en ny tråd och låter den peka på en annan metod.

using System.Threading;

class MainClass {
    static void Main() {
        var thread = new Thread(Secondary);
        thread.Start();
    }

    static void Secondary() {
        System.Console.WriteLine("Hello World!");
    }
}

Starta en tråd med parametrar

använder System.Treading;

class MainClass {
    static void Main() {
        var thread = new Thread(Secondary);
        thread.Start("SecondThread");
    }

    static void Secondary(object threadName) {
        System.Console.WriteLine("Hello World from thread: " + threadName);
    }
}

Skapa en tråd per processor

Environment.ProcessorCount Hämtar antalet logiska processorer på den aktuella maskinen.

CLR planerar sedan varje tråd till en logisk processor, detta kan teoretiskt betyda varje tråd på en annan logisk processor, alla trådar på en enda logisk processor eller någon annan kombination.

using System;
using System.Threading;

class MainClass {
    static void Main() {
        for (int i = 0; i < Environment.ProcessorCount; i++) {
            var thread = new Thread(Secondary);
            thread.Start(i);
        }
        
    }

    static void Secondary(object threadNumber) {
        System.Console.WriteLine("Hello World from thread: " + threadNumber);
    }
}

Undvika att läsa och skriva data samtidigt

Ibland vill du att dina trådar ska dela data samtidigt. När detta händer är det viktigt att vara medveten om koden och låsa alla delar som kan gå fel. Ett enkelt exempel på två räknar trådar visas nedan.

Här är någon farlig (felaktig) kod:

using System.Threading;

class MainClass 
{    
    static int count { get; set; }

    static void Main() 
    {
        for (int i = 1; i <= 2; i++)
        {
            var thread = new Thread(ThreadMethod);
            thread.Start(i);
            Thread.Sleep(500);
        }
    }

    static void ThreadMethod(object threadNumber) 
    {
        while (true)
        {
            var temp = count;
            System.Console.WriteLine("Thread " + threadNumber + ": Reading the value of count.");
            Thread.Sleep(1000);
            count = temp + 1;
            System.Console.WriteLine("Thread " + threadNumber + ": Incrementing the value of count to:" + count);
            Thread.Sleep(1000);
        }
    }
}

Du kommer att märka att istället för att räkna 1,2,3,4,5 ... räknar vi 1,1,2,2,3 ...

För att åtgärda problemet måste vi låsa räknarvärdet så att flera olika trådar inte kan läsa och skriva till det samtidigt. Med tillägg av ett lås och en nyckel kan vi förhindra att trådarna får åtkomst till data samtidigt.

using System.Threading;

class MainClass
{

    static int count { get; set; } 
    static readonly object key = new object();

    static void Main()
    {
        for (int i = 1; i <= 2; i++)
        {
            var thread = new Thread(ThreadMethod);
            thread.Start(i);
            Thread.Sleep(500);
        }
    }

    static void ThreadMethod(object threadNumber)
    {
        while (true)
        {
            lock (key) 
            {
                var temp = count;
                System.Console.WriteLine("Thread " + threadNumber + ": Reading the value of count.");
                Thread.Sleep(1000);
                count = temp + 1;
                System.Console.WriteLine("Thread " + threadNumber + ": Incrementing the value of count to:" + count);
            }
            Thread.Sleep(1000);
        }
    }
}

Parallell.ForEach Loop

Om du har en förhandslinga som du vill påskynda och inte bryr dig om vilken ordning utgången är i kan du konvertera den till en parallell förhandslinga genom att göra följande:

using System;
using System.Threading;
using System.Threading.Tasks;

public class MainClass {

    public static void Main() {
        int[] Numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        // Single-threaded
        Console.WriteLine("Normal foreach loop: ");
        foreach (var number in Numbers) {
            Console.WriteLine(longCalculation(number));
        }
        // This is the Parallel (Multi-threaded solution)
        Console.WriteLine("Parallel foreach loop: ");
        Parallel.ForEach(Numbers, number => {
            Console.WriteLine(longCalculation(number));
        });
    }

    private static int longCalculation(int number) {
        Thread.Sleep(1000); // Sleep to simulate a long calculation
        return number * number;
    }
}

Dödlås (två trådar väntar på varandra)

En dödlås är vad som händer när två eller flera trådar väntar på varandra att slutföra eller släppa en resurs på ett sådant sätt att de väntar för evigt.

Ett typiskt scenario med två trådar som väntar på varandra att slutföra är när en Windows Forms GUI-tråd väntar på en arbetartråd och arbetartråden försöker åberopa ett objekt som hanteras av GUI-tråden. Observera att med denna kod exmaple, genom att klicka på knappen1 kommer programmet att hänga.

private void button1_Click(object sender, EventArgs e)
{
    Thread workerthread= new Thread(dowork);
    workerthread.Start();
    workerthread.Join();
    // Do something after
}

private void dowork()
{
    // Do something before
    textBox1.Invoke(new Action(() => textBox1.Text = "Some Text"));
    // Do something after
}

workerthread.Join() är ett samtal som blockerar den ringande tråden tills arbetartråden är klar. textBox1.Invoke(invoke_delegate) är ett samtal som blockerar den ringande tråden tills GUI-tråden har bearbetat invoke_delegate, men detta samtal orsakar dödlås om GUI-tråden redan väntar på att samtalstråden slutförs.

För att komma runt detta kan man använda ett icke-blockerande sätt att åberopa textrutan istället:

private void dowork()
{
    // Do work
    textBox1.BeginInvoke(new Action(() => textBox1.Text = "Some Text"));
    // Do work that is not dependent on textBox1 being updated first
}

Detta kommer dock att orsaka problem om du behöver köra kod som är beroende av att textrutan uppdateras först. Kör i så fall det som en del av åkallandet, men var medveten om att det kommer att göra att det körs på GUI-tråden.

private void dowork()
{
    // Do work
    textBox1.BeginInvoke(new Action(() => {
        textBox1.Text = "Some Text";
        // Do work dependent on textBox1 being updated first, 
        // start another worker thread or raise an event
    }));
    // Do work that is not dependent on textBox1 being updated first
}

Starta alternativt en helt ny tråd och låt den göra väntan på GUI-tråden så att arbetartråden kan slutföra.

private void dowork()
{
    // Do work
    Thread workerthread2 = new Thread(() =>
    {
        textBox1.Invoke(new Action(() => textBox1.Text = "Some Text"));
        // Do work dependent on textBox1 being updated first, 
        // start another worker thread or raise an event
    });
    workerthread2.Start();
    // Do work that is not dependent on textBox1 being updated first
}

För att minimera risken för att stöta på ett dödläge av ömsesidig vänta, undvik alltid cirkulära referenser mellan trådarna när det är möjligt. En hierarki av trådar där lägre rangordnade trådar bara lämnar meddelanden för högre rangordnade trådar och aldrig väntar på dem kommer inte att stöta på den här typen av problem. Det skulle dock fortfarande vara sårbart för dödlås baserat på resurslåsning.

Dödlås (håll resurs och vänta)

En dödlås är vad som händer när två eller flera trådar väntar på varandra att slutföra eller släppa en resurs på ett sådant sätt att de väntar för evigt.

Om tråd1 håller ett lås på resurs A och väntar på att resurs B ska släppas medan tråd2 håller resurs B och väntar på att resurs A kommer att släppas, är de låsta.

Om du klickar på knapp 1 för följande exempelkod kommer din ansökan att komma i ovan nämnda inlåst tillstånd och hänga

private void button_Click(object sender, EventArgs e)
{
    DeadlockWorkers workers = new DeadlockWorkers();
    workers.StartThreads();
    textBox.Text = workers.GetResult();
}

private class DeadlockWorkers
{
    Thread thread1, thread2;

    object resourceA = new object();
    object resourceB = new object();

    string output;

    public void StartThreads()
    {
        thread1 = new Thread(Thread1DoWork);
        thread2 = new Thread(Thread2DoWork);
        thread1.Start();
        thread2.Start();
    }

    public string GetResult()
    {
        thread1.Join();
        thread2.Join();
        return output;
    }

    public void Thread1DoWork()
    {
        Thread.Sleep(100);
        lock (resourceA)
        {
            Thread.Sleep(100);
            lock (resourceB)
            {
                output += "T1#";
            }
        }
    }

    public void Thread2DoWork()
    {
        Thread.Sleep(100);
        lock (resourceB)
        {
            Thread.Sleep(100);
            lock (resourceA)
            {
                output += "T2#";
            }
        }
    }
}

För att undvika att vara låst på detta sätt kan man använda Monitor.TryEnter (lock_object, timeout_in_milliseconds) för att kontrollera om ett lås finns kvar på ett objekt redan. Om Monitor.TryEnter inte lyckas skaffa ett lås på lock_object före timeout_in_millisekunder, returnerar det falskt, vilket ger tråden en chans att släppa andra hållna resurser och ge, vilket ger andra trådar en chans att slutföra som i denna något modifierade version av ovanstående :

private void button_Click(object sender, EventArgs e)
{
    MonitorWorkers workers = new MonitorWorkers();
    workers.StartThreads();
    textBox.Text = workers.GetResult();
}

private class MonitorWorkers
{
    Thread thread1, thread2;

    object resourceA = new object();
    object resourceB = new object();

    string output;

    public void StartThreads()
    {
        thread1 = new Thread(Thread1DoWork);
        thread2 = new Thread(Thread2DoWork);
        thread1.Start();
        thread2.Start();
    }

    public string GetResult()
    {
        thread1.Join();
        thread2.Join();
        return output;
    }

    public void Thread1DoWork()
    {
        bool mustDoWork = true;
        Thread.Sleep(100);
        while (mustDoWork)
        {
            lock (resourceA)
            {
                Thread.Sleep(100);
                if (Monitor.TryEnter(resourceB, 0))
                {
                    output += "T1#";
                    mustDoWork = false;
                    Monitor.Exit(resourceB);
                }
            }
            if (mustDoWork) Thread.Yield();
        }
    }

    public void Thread2DoWork()
    {
        Thread.Sleep(100);
        lock (resourceB)
        {
            Thread.Sleep(100);
            lock (resourceA)
            {
                output += "T2#";
            }
        }
    }
}

Observera att denna lösning förlitar sig på att tråd2 är envis om att dess lås och tråd1 är villig att ge, så att tråd2 alltid har företräde. Observera också att thread1 måste göra om det arbete som det gjorde efter att låsa resurs A när det ger. Var därför försiktig när du implementerar detta tillvägagångssätt med mer än en givande tråd, eftersom du då riskerar att gå in i en så kallad boskap - ett tillstånd som skulle inträffa om två trådar fortsatte att göra den första biten av sitt arbete och sedan ge varandra , börjar upprepade gånger.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow