跳至內容

C Sharp/多線程與同步

維基教科書,自由的教學讀本

C#提供了至少六種多線程並發的實現:

  1. 線程類:var t = new Thread(BackgroundTask);t.Start("Thread");
  2. 異步委託:Action<string> d = BackgroundTask; d.BeginInvoke("BeginInvoke", null, null);
  3. 線程池:ThreadPool.QueueUserWorkItem(BackgroundTask, "ThreadPool");
  4. BackgroundWorker類:BackgroundWorker _demoBGWorker = new BackgroundWorker(); _demoBGWorker.DoWork += myWorkHorse;_demoBGWorker.RunWorkerAsync();
  5. Task Parallel Library (TPL):Task.Run(() => BackgroundTask("TPL"));
  6. async await

Thread

[編輯]

System.Threading.Thread類提供了基本功能。創建Thread類實例,參數為ThreadStartParameterizedThreadStart指向委託函數。如下例:

using System;
using System.Threading;

public static class Program
{
    private static void SecondThreadFunction()
    {
        while (true)
        {
            Console.WriteLine("Second thread says hello.");
            Thread.Sleep(1000); // pause execution of the current thread for 1 second (1000 ms)
        }
    }
    
    public static void Main()
    {
        Thread newThread = new Thread(new ThreadStart(SecondThreadFunction));
        
        newThread.Start();
        
        while (true)
        {
            Console.WriteLine("First thread says hello.");
            Thread.Sleep(500); // pause execution of the current thread for half a second (500 ms)
        }
    }
}

void ParameterizedThreadStart(object obj)委託允許給新線程傳遞一個參數:

using System;
using System.Threading;

public static class Program
{
    private static void SecondThreadFunction(object param)
    {
        while (true)
        {
            Console.WriteLine("Second thread says " + param.ToString() + ".");
            Thread.Sleep(500); // pause execution of the current thread for half a second (500 ms)
        }
    }
    
    public static void Main()
    {
        Thread newThread = new Thread(new ParameterizedThreadStart(SecondThreadFunction));
        
        newThread.Start(1234); // here you pass a parameter to the new thread
        
        while (true)
        {
            Console.WriteLine("First thread says hello.");
            Thread.Sleep(1000); // pause execution of the current thread for a second (1000 ms)
        }
    }
}

也可寫成:

using System;
using System.Threading;

            Thread t2 = new Thread(() => { MyWorkHorseFunc(MyParam1); }); 
            t2.Start();

共享數據

[編輯]

下例2個線程訪問同一個變量,並不安全:

using System;
using System.Threading;

public static class Program
{
    public static void Main()
    {
        int number = 1;
        Thread newThread = new Thread(new ThreadStart(delegate
        {
            while (true)
            {
                number++;
                Console.WriteLine("Second thread says " + number.ToString() + ".");
                Thread.Sleep(1000);
            }
        }));
        
        newThread.Start();
        
        while (true)
        {
            number++;
            Console.WriteLine("First thread says " + number.ToString() + ".");
            Thread.Sleep(1000);
        }
    }
}

異步委託

[編輯]
using System;

public static class Program
{
     delegate int del(int[] data);
     
     public static int SumOfNumbers(int[] data)
     {
          int sum = 0;
          foreach (int number in data) {
               sum += number;
          }

          return sum;
     }

     public static void Main()
     {
          int[] numbers = new int[] { 1, 2, 3, 4, 5 };
          del func = SumOfNumbers;
          IAsyncResult result = func.BeginInvoke(numbers, null, null);//异步调用,不阻塞
          
          // I can do stuff here while numbers is being added
          
          int sum = func.EndInvoke(result);//等待异步调用完成
           //sum = 15
     }
}

同步

[編輯]

C#支持lock關鍵字,並在退出其作用域時解鎖:

using System;
using System.Threading;

public static class Program
{
    public static void Main()
    {
        int number = 1;
        object numberLock = new object();
        Thread newThread = new Thread(new ThreadStart(delegate
        {
            while (true)
            {
                lock (numberLock)
                {
                    number++;
                    Console.WriteLine("Second thread says " + number.ToString() + ".");
                }

                Thread.Sleep(1000);
            }
        }));
        
        newThread.Start();
        
        while (true)
        {
            lock (numberLock)
            {
                number++;
                Console.WriteLine("First thread says " + number.ToString() + ".");
            }

            Thread.Sleep(1000);
        }
    }
}


Thread類的Join方法允許線程會合:

using System;
using System.Threading;

public static class Program
{
    public static void Main()
    {
        Thread newThread = new Thread(new ThreadStart(delegate
        {
            Console.WriteLine("Second thread reporting.");
            Thread.Sleep(5000);
            Console.WriteLine("Second thread done sleeping.");
        }));

        newThread.Start();
        Console.WriteLine("Just started second thread.");
        newThread.Join(1000);
        Console.WriteLine("First thread waited for 1 second.");
        newThread.Join();
        Console.WriteLine("First thread finished waiting for second thread. Press any key.");
        Console.ReadKey();
    }
}

任務並行庫

[編輯]

.NET 4引入了 Task Parallel Library (TPL)是當時最好的創建後台任務的方式。有強大的模型,支持鏈式任務、並行執行、等待一個或多個任務完成,傳遞cancellation token,甚至控制後台線程。


async和await

[編輯]

C# 5和.NET 4.5引入了async和await。這允許你按照同步的方式寫原始碼但異步執行。可以await任何返回task的方法,對普通方法也可以用TPL包裝後await:

await Task.Run(() => xdoc.Load("http://feeds.feedburner.com/soundcode"));

一個特別好處是在UI線程中await一個方法時,會返回UI線程恢復執行:

await Task.Run(() => xdoc.Load("http://feeds.feedburner.com/soundcode"));
label1.Text = "Done"; // we’re back on the UI thread!


還可以使用try和catch包住異步線程:

private async void OnButtonAsyncAwaitClick(object sender, EventArgs e)
{
    const string state = "Async Await";
    this.Cursor = Cursors.WaitCursor;
    try
    {
        label1.Text = String.Format("{0} Started", state);
        await AwaitableBackgroundTask(state);
        label1.Text = String.Format("About to load XML"); 
        var xdoc = new XmlDocument();
        await Task.Run(() => xdoc.Load("http://feeds.feedburner.com/soundcode"));
        label1.Text = String.Format("{0} Done {1}", state, xdoc.FirstChild.Name);
    }
    catch (Exception ex)
    {
        label1.Text = ex.Message;
    }
    finally
    {
        this.Cursor = Cursors.Default;
    }
}