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;
    }
}