Thread
- 创建一个方法作为新线程的入口点。
- 创建一个 ThreadStart 或者ParameterizedThreadStart 的委托,并把在上一步所定义方法的地址传给委托的构造函数。
- 创建一个Thread的对象,并把ThreadStart 或者ParameterizedThreadStart 委托作为构造函数的参数。
- 创建任意初始化线程的特性(名称、优先级)
- 调用Thread.Start()方法。
在第(2)步中建立的委托指向的方法将在线程中进口执行。
ThreadStart 指向的方法没有参数和返回值的方法。 局限性是用户无法给过程传递参数。
ParameterizedThreadStart 有参数无返回值。 参数可以通过结构或者类传递参数。System.oBJECT.
0. System.Threading 命名空间的成员
ThreadStart 委托
using System.Runtime.InteropServices;
namespace System.Threading
{
[ComVisible(true)]
public delegate void ThreadStart();
}
#if false // 反编译日志
缓存中的 9 项
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace SimpleMultiThreadApp
{
#region The Printer class
public class Printer
{
public void PrintNumbers()
{
// Display Thread info.
Console.WriteLine("-> {0} is executing PrintNumbers()",
Thread.CurrentThread.Name);
// Print out numbers.
Console.Write("Your numbers: ");
for (int i = 0; i < 10; i++)
{
Console.Write("{0}, ", i);
Thread.Sleep(2000);
}
Console.WriteLine();
}
}
#endregion
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** The Amazing Thread App *****\n");
Console.Write("Do you want [1] or [2] threads? ");
string threadCount = Console.ReadLine();
// Name the current thread.
Thread primaryThread = Thread.CurrentThread;
primaryThread.Name = "Primary";
// Display Thread info.
Console.WriteLine("-> {0} is executing Main()",
Thread.CurrentThread.Name);
// Make worker class.
Printer p = new Printer();
switch (threadCount)
{
case "2":
// Now make the thread.
Thread backgroundThread =
new Thread(new ThreadStart(p.PrintNumbers));
backgroundThread.Name = "Secondary";
backgroundThread.Start();
break;
case "1":
p.PrintNumbers();
break;
default:
Console.WriteLine("I don't know what you want...you get 1 thread.");
goto case "1";
}
// Do some additional work.
MessageBox.Show("I'm busy!", "Work on main thread...");
Console.ReadLine();
}
}
}
ParameterizedThreadStart 委托
using System.Runtime.InteropServices;
namespace System.Threading
{
[ComVisible(false)]
public delegate void ParameterizedThreadStart(object obj);
}
#if false // 反编译日志
缓存中的 8 项
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace AddWithThreads
{
#region The AddParams class
class AddParams
{
public int a, b;
public AddParams(int numb1, int numb2)
{
a = numb1;
b = numb2;
}
}
#endregion
class Program
{
private static AutoResetEvent waitHandle = new AutoResetEvent(false);
static void Main(string[] args)
{
Console.WriteLine("***** Adding with Thread objects *****");
Console.WriteLine("ID of thread in Main(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = new AddParams(10, 10);
Thread t = new Thread(new ParameterizedThreadStart(Add));
t.Start(ap);
// Wait here until you are notified
waitHandle.WaitOne();
Console.WriteLine("Other thread is done!");
Console.ReadLine();
}
static void Add(object data)
{
if (data is AddParams)
{
Console.WriteLine("ID of thread in Add(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = (AddParams)data;
Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b);
// Tell other thread we are done.
waitHandle.Set();
}
}
}
}
TimerCallback 委托
using System.Runtime.InteropServices;
namespace System.Threading
{
[ComVisible(true)]
[__DynamicallyInvokable]
public delegate void TimerCallback(object state);
}
#if false // 反编译日志
缓存中的 8 项
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace TimerApp
{
class Program
{
static void PrintTime(object state)
{
Console.WriteLine("Time is: {0}",
DateTime.Now.ToLongTimeString());
}
static void Main(string[] args)
{
Console.WriteLine("***** Working with Timer type *****\n");
// Create the delegate for the Timer type.
TimerCallback timeCB = new TimerCallback(PrintTime);
// Establish timer settings.
Timer t = new Timer(
timeCB, // The TimerCallback delegate type.
"Hello From Main", // Any info to pass into the called method (null for no info).
0, // Amount of time to wait before starting.
1000); // Interval of time between calls (in milliseconds).
Console.WriteLine("Hit key to terminate...");
Console.ReadLine();
}
}
}
ThreadPool
线程池的好处:
- 线程池减少了线程的创建、开始和停止次数,这就是提高了效率。
- 使用线程池,能够使我们将注意力放到业务上而不是多线程的架构上。
- 线程池中线程总是后台线程,且他的线程优先级默认的是Nornal。
线程的好处:
- 如果需要前台线程或者设置线程优先级。
- 如果需要有一个带有固定标识的线程便于退出、挂起、通过名称发现他。
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Security;
using System.Security.Permissions;
namespace System.Threading
{
[__DynamicallyInvokable]
[HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
public static class ThreadPool
{
[SecuritySafeCritical]
[SecurityPermission(SecurityAction.Demand, ControlThread = true)]
public static bool SetMaxThreads(int workerThreads, int completionPortThreads)
{
return SetMaxThreadsNative(workerThreads, completionPortThreads);
}
[SecuritySafeCritical]
public static void GetMaxThreads(out int workerThreads, out int completionPortThreads)
{
GetMaxThreadsNative(out workerThreads, out completionPortThreads);
}
[SecuritySafeCritical]
[SecurityPermission(SecurityAction.Demand, ControlThread = true)]
public static bool SetMinThreads(int workerThreads, int completionPortThreads)
{
return SetMinThreadsNative(workerThreads, completionPortThreads);
}
using System.Runtime.InteropServices;
namespace System.Threading
{
[ComVisible(true)]
[__DynamicallyInvokable]
public delegate void WaitCallback(object state);
}
#if false // 反编译日志
缓存中的 8 项
#endif
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ThreadPoolApp
{
#region Helper class
public class Printer
{
private object lockToken = new object();
public void PrintNumbers()
{
lock (lockToken)
{
// Display Thread info.
Console.WriteLine("-> {0} is executing PrintNumbers()",
Thread.CurrentThread.ManagedThreadId);
// Print out numbers.
Console.Write("Your numbers: ");
for (int i = 0; i < 10; i++)
{
Console.Write("{0}, ", i);
Thread.Sleep(1000);
}
Console.WriteLine();
}
}
}
#endregion
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with the CLR Thread Pool *****\n");
Console.WriteLine("Main thread started. ThreadID = {0}",
Thread.CurrentThread.ManagedThreadId);
Printer p = new Printer();
WaitCallback workItem = new WaitCallback(PrintTheNumbers);
// Queue the method 10 times 将方法排队10次
for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(workItem, p);
}
Console.WriteLine("All tasks queued");
Console.ReadLine();
}
static void PrintTheNumbers(object state)
{
Printer task = (Printer)state;
task.PrintNumbers();
}
}
}
本文主要来自一道面试题,由于之前对AutoResetEvent的概念比较模糊(即使已经使用过了)。面试题题目很简洁:两个线程交替打印0~100的奇偶数。你可以先动手试试,我主要是尝试在一个方法里面完成这个任务。
注: Suspend,Resume来控制线程已经在.net framework2.0被淘汰了,原因就是挂起之后,但因为异常而没有及时恢复,如果占用资源会导致死锁。
AutoResetEvent 类概念#
AutoResetEvent对象用来进行线程同步操作,AutoResetEvent类继承waitHandle类。waitOne()方法就继承来自waitHandle类。
AutoResetEvent对象有终止和非终止两种状态,终止状态是线程继续执行,非终止状态使线程阻塞,可以调用set和reset方法使对象进入终止和非终止状态。-》可以简单理解如果AutoResetEvent对象是终止状态,就像不管别人了,任你撒野去(waitOne()得到的都是撒野信号)
AutoResetEvent顾名思义,其对象在调用一次set之后会自动调用一次reset,进入非终止状态使调用了等待方法的线程进入阻塞状态。-》可以简单理解如果AutoResetEvent对象是非终止状态,就开始管理起别人来了,此时waitOne()得到的信号都是呆在原地不动信号。
waitHandle对象的waitone可以使当前线程进入阻塞状态,等待一个信号。直到当前 waitHandle对象收到信号,才会继续执行。
set可以发送一个信号,允许一个调用waitone而等待线程继续执行。 ManulResetEvent的set方法可以允许多个。但是要手动关闭,即调用reset();
reset可以使因为调用waitone() 而等待线程都进入阻塞状态。
AutoResetEvent主要方法及实践#
AutoResetEvent(bool initialState):构造函数,用一个指示是否将初始状态设置为终止的布尔值初始化该类的新实例。 false:无信号,子线程的WaitOne方法不会被自动调用 true:有信号,子线程的WaitOne方法会被自动调用
Reset ():将事件状态设置为非终止状态,导致线程阻止;如果该操作成功,则返回true;否则,返回false。
Set ():将事件状态设置为终止状态,允许一个或多个等待线程继续;如果该操作成功,则返回true;否则,返回false。
WaitOne(): 阻止当前线程,直到收到信号。
WaitOne(TimeSpan, Boolean) :阻止当前线程,直到当前实例收到信号,使用 TimeSpan 度量时间间隔并指定是否在等待之前退出同步域。
有了上面的解释,开始展示代码(经过多次优化)
//若要将初始状态设置为终止,则为 true;若要将初始状态设置为非终止,则为 false
static AutoResetEvent oddResetEvent = new AutoResetEvent(false);
static AutoResetEvent evenResetEvent = new AutoResetEvent(false);
static int i = 0;
static void Main(string[] args)
{
//ThreadStart是个委托
Thread thread1 = new Thread(new ThreadStart(show));
thread1.Name = "偶数线程";
Thread thread2 = new Thread(new ThreadStart(show));
thread2.Name = "奇数线程";
thread1.Start();
Thread.Sleep(2); //保证偶数线程先运行。
thread2.Start();
Console.Read();
}
public static void show()
{
while (i <= 100)
{
int num = i % 2;
if (num == 0)
{
Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, "evenResetEvent");
if(i!=1) evenResetEvent.Set();
oddResetEvent.WaitOne(); //当前线程阻塞
}
else
{
Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, "oddResetEvent");
//如果此时AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。否则不会被阻止
oddResetEvent.Set();
evenResetEvent.WaitOne();
}
}
}
第二种方法Semaphore#
此外,我们利用信号量也可以实现,信号量是一种内核模式锁,对性能要求比较高,特殊情况下才考虑使用,而且要避免在内核模式和用户模式下频繁相互切换线程。代码如下:
private static readonly int MaxSize = 1;
private static int i = 0;
static Semaphore oddSemaphore = new Semaphore(0, MaxSize);
static Semaphore evenSemaphore = new Semaphore(0, MaxSize);
static void Main(string[] args)
{
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
//ThreadStart是个委托
Thread thread1 = new Thread(new ThreadStart(show));
thread1.Name = "偶数线程";
Thread thread2 = new Thread(new ThreadStart(show));
thread2.Name = "奇数线程";
thread1.Start();
thread2.Start();
thread1.Join();
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds);
Console.Read();
}
private static void show()
{
if(i==1) evenSemaphore.WaitOne();
while (i <= 100)
{
int num = i % 2;
if (num == 0)
{
Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId);
evenSemaphore.Release();
oddSemaphore.WaitOne(); //当前线程阻塞
}
else
{
Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId);
//释放一个偶数信号空位出来;
oddSemaphore.Release();
evenSemaphore.WaitOne(); //当前线程阻塞
//此时已经消耗了一个奇数信号空位
}
}
}
第三种方法,约定每个线程只干自己的事#
这种方法利用线程池本身就是队列的方式,即先进先出。测试之后发现性能有下降,但是还是贴出来供参考。
static int threadCount = 2;
static int count = 0;
static object cursorLock = new object();
static void Main(string[] args)
{
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
Task[] arr = new Task[2];
for (int threadIndex = 0; threadIndex < threadCount; threadIndex++)
{
//这两种方法都可以
arr[threadIndex] = Task.Factory.StartNew(PrintNum, threadIndex);
}
Task.WaitAll(arr);
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds);
Console.Read();
}
private static void PrintNum(object num)
{
bool isOk = false;
while (!isOk)
{
lock (cursorLock)
{
int index = count % 2;
if (count>100)
{
isOk = true;
}
else if (index == (int)num)
{
if (index == 0) Console.WriteLine("{0}:{1} {2} ", "偶数线程", Thread.CurrentThread.ManagedThreadId, count++);
else Console.WriteLine("{0}:{1} {2} ", "奇数线程", Thread.CurrentThread.ManagedThreadId, count++);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace AddWithThreads
{
#region The AddParams class
class AddParams
{
public int a, b;
public AddParams(int numb1, int numb2)
{
a = numb1;
b = numb2;
}
}
#endregion
class Program
{
private static AutoResetEvent waitHandle = new AutoResetEvent(false);
static void Main(string[] args)
{
Console.WriteLine("***** Adding with Thread objects *****");
Console.WriteLine("ID of thread in Main(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = new AddParams(10, 10);
Thread t = new Thread(new ParameterizedThreadStart(Add));
t.Start(ap);
// Wait here until you are notified
waitHandle.WaitOne();
Console.WriteLine("Other thread is done!");
Console.ReadLine();
}
static void Add(object data)
{
if (data is AddParams)
{
Console.WriteLine("ID of thread in Add(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = (AddParams)data;
Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b);
// Tell other thread we are done.
waitHandle.Set();
}
}
}
}
using System.Runtime.InteropServices;
namespace System.Threading
{
[ComVisible(false)]
public delegate void ParameterizedThreadStart(object obj);
参数化线程
}
#if false // 反编译日志
缓存中的 8 项
#endif
并发问题
1. lock 关键字进行同步
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace MultiThreadedPrinting
{
#region Printer helper class
public class Printer
{
// Lock token. 锁定令牌。
private object threadLock = new object();
public void PrintNumbers()
{
lock (threadLock)
{
// Display Thread info.
Console.WriteLine("-> {0} is executing PrintNumbers()",
Thread.CurrentThread.Name);
// Print out numbers.
Console.Write("Your numbers: ");
for (int i = 0; i < 10; i++)
{
Random r = new Random();
Thread.Sleep(100 * r.Next(5));
Console.Write("{0}, ", i);
}
Console.WriteLine();
}
}
}
#endregion
class Program
{
static void Main(string[] args)
{
Console.WriteLine("*****Synchronizing Threads *****\n");
Printer p = new Printer();
//制作10个都指向同一个线程
//方法。
// Make 10 threads that are all pointing to the same
// method on the same object.
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++)
{
threads[i] =
new Thread(new ThreadStart(p.PrintNumbers));
threads[i].Name = string.Format("Worker thread #{0}", i);
}
// Now start each one.
foreach (Thread t in threads)
t.Start();
Console.ReadLine();
}
}
}
2. Monitor 关键字进行同步
public void PrintNumbers2()
{
Monitor.Enter(threadLock);
try
{
// Display Thread info.
Console.WriteLine("-> {0} is executing PrintNumbers()",
Thread.CurrentThread.Name);
// Print out numbers.
Console.Write("Your numbers: ");
for (int i = 0; i < 10; i++)
{
Random r = new Random();
Thread.Sleep(100 * r.Next(5));
Console.Write("{0}, ", i);
}
Console.WriteLine();
}
finally
{
Monitor.Exit(threadLock);
}
}
}
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
namespace System.Threading
{
[ComVisible(true)]
[__DynamicallyInvokable]
[HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
public static class Monitor
{
[MethodImpl(MethodImplOptions.InternalCall)]
[SecuritySafeCritical]
[__DynamicallyInvokable]
public static extern void Enter(object obj);
[__DynamicallyInvokable]
public static void Enter(object obj, ref bool lockTaken)
{
if (lockTaken)
{
ThrowLockTakenException();
}
ReliableEnter(obj, ref lockTaken);
}
private static void ThrowLockTakenException()
{
throw new ArgumentException(Environment.GetResourceString("Argument_MustBeFalse"), "lockTaken");
}
[MethodImpl(MethodImplOptions.InternalCall)]
[SecuritySafeCritical]
private static extern void ReliableEnter(object obj, ref bool lockTaken);
[MethodImpl(MethodImplOptions.InternalCall)]
[SecuritySafeCritical]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[__DynamicallyInvokable]
public static extern void Exit(object obj);
[__DynamicallyInvokable]
public static bool TryEnter(object obj)
{
bool lockTaken = false;
TryEnter(obj, 0, ref lockTaken);
return lockTaken;
}
[__DynamicallyInvokable]
public static void TryEnter(object obj, ref bool lockTaken)
{
if (lockTaken)
{
ThrowLockTakenException();
}
ReliableEnterTimeout(obj, 0, ref lockTaken);
}
[__DynamicallyInvokable]
public static bool TryEnter(object obj, int millisecondsTimeout)
{
bool lockTaken = false;
TryEnter(obj, millisecondsTimeout, ref lockTaken);
return lockTaken;
}
private static int MillisecondsTimeoutFromTimeSpan(TimeSpan timeout)
{
long num = (long)timeout.TotalMilliseconds;
if (num < -1 || num > int.MaxValue)
{
throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1"));
}
return (int)num;
}
[__DynamicallyInvokable]
public static bool TryEnter(object obj, TimeSpan timeout)
{
return TryEnter(obj, MillisecondsTimeoutFromTimeSpan(timeout));
}
[__DynamicallyInvokable]
public static void TryEnter(object obj, int millisecondsTimeout, ref bool lockTaken)
{
if (lockTaken)
{
ThrowLockTakenException();
}
ReliableEnterTimeout(obj, millisecondsTimeout, ref lockTaken);
}
[__DynamicallyInvokable]
public static void TryEnter(object obj, TimeSpan timeout, ref bool lockTaken)
{
if (lockTaken)
{
ThrowLockTakenException();
}
ReliableEnterTimeout(obj, MillisecondsTimeoutFromTimeSpan(timeout), ref lockTaken);
}
[MethodImpl(MethodImplOptions.InternalCall)]
[SecuritySafeCritical]
private static extern void ReliableEnterTimeout(object obj, int timeout, ref bool lockTaken);
[SecuritySafeCritical]
[__DynamicallyInvokable]
public static bool IsEntered(object obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
return IsEnteredNative(obj);
}
[MethodImpl(MethodImplOptions.InternalCall)]
[SecurityCritical]
private static extern bool IsEnteredNative(object obj);
[MethodImpl(MethodImplOptions.InternalCall)]
[SecurityCritical]
private static extern bool ObjWait(bool exitContext, int millisecondsTimeout, object obj);
[SecuritySafeCritical]
public static bool Wait(object obj, int millisecondsTimeout, bool exitContext)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
return ObjWait(exitContext, millisecondsTimeout, obj);
}
public static bool Wait(object obj, TimeSpan timeout, bool exitContext)
{
return Wait(obj, MillisecondsTimeoutFromTimeSpan(timeout), exitContext);
}
[__DynamicallyInvokable]
public static bool Wait(object obj, int millisecondsTimeout)
{
return Wait(obj, millisecondsTimeout, exitContext: false);
}
[__DynamicallyInvokable]
public static bool Wait(object obj, TimeSpan timeout)
{
return Wait(obj, MillisecondsTimeoutFromTimeSpan(timeout), exitContext: false);
}
[__DynamicallyInvokable]
public static bool Wait(object obj)
{
return Wait(obj, -1, exitContext: false);
}
[MethodImpl(MethodImplOptions.InternalCall)]
[SecurityCritical]
private static extern void ObjPulse(object obj);
[SecuritySafeCritical]
[__DynamicallyInvokable]
public static void Pulse(object obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
ObjPulse(obj);
}
[MethodImpl(MethodImplOptions.InternalCall)]
[SecurityCritical]
private static extern void ObjPulseAll(object obj);
[SecuritySafeCritical]
[__DynamicallyInvokable]
public static void PulseAll(object obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
ObjPulseAll(obj);
}
}
}
#if false // 反编译日志
缓存中的 8 项
#endif
3. System.Threading.Interlocked关键字进行同步
namespace System.Threading
{
[__DynamicallyInvokable]
public static class Interlocked
{
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[__DynamicallyInvokable]
public static int Increment(ref int location)
{
return Add(ref location, 1);
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[__DynamicallyInvokable]
public static long Increment(ref long location)
{
return Add(ref location, 1L);
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[__DynamicallyInvokable]
public static int Decrement(ref int location)
{
return Add(ref location, -1);
}
[__DynamicallyInvokable]
public static long Decrement(ref long location)
{
return Add(ref location, -1L);
}
4. 使用 [Synchronization] 特性进行同步
5. 使用ReaderWriterLock类实现多用户读/单用户写同步
摘要:C#提供了System.Threading.ReaderWriterLock类以适应多用户读/单用户写的场景。该类可实现以下功能:如果资源未被写操作锁定,那么任何线程都可对该资源进行读操作锁定,并且对读操作锁数量没有限制,即多个线程可同时对该资源进行读操作锁定,以读取数据。
使用Monitor或Mutex进行同步控制的问题:由于独占访问模型不允许任何形式的并发访问,这样的效率总是不太高。许多时候,应用程序在访问资源时是进行读操作,写操作相对较少。为解决这一问题,C#提供了System.Threading.ReaderWriterLock类以适应多用户读/单用户写的场景。该类可实现以下功能:如果资源未被写操作锁定,那么任何线程都可对该资源进行读操作锁定,并且对读操作锁数量没有限制,即多个线程可同时对该资源进行读操作锁定,以读取数据。如果资源未被添加任何读或写操作锁,那么一个且仅有一个线程可对该资源添加写操作锁定,以写入数据。简单的讲就是:读操作锁是共享锁,允许多个线程同时读取数据;写操作锁是独占锁,同一时刻,仅允许一个线程进行写操作。
using System;
using System.Threading;
namespace ProcessTest
{
class Program
{
//资源
static int theResource = 0;
//读、写操作锁
static ReaderWriterLock rwl = new ReaderWriterLock();
static void Main(string[] args)
{
//分别创建2个读操作线程,2个写操作线程,并启动
Thread tr0 = new Thread(new ThreadStart(Read));
Thread tr1 = new Thread(new ThreadStart(Read));
Thread tr2 = new Thread(new ThreadStart(Write));
Thread tr3 = new Thread(new ThreadStart(Write));
tr0.Start();
tr1.Start();
tr2.Start();
tr3.Start();
//等待线程执行完毕
tr0.Join();
tr1.Join();
tr2.Join();
tr3.Join();
System.Console.ReadKey();
}
//读数据
static void Read()
{
for (int i = 0; i < 3; i++)
{
try
{
//申请读操作锁,如果在1000ms内未获取读操作锁,则放弃
rwl.AcquireReaderLock(1000);
Console.WriteLine("开始读取数据,theResource = {0}", theResource);
Thread.Sleep(10);
Console.WriteLine("读取数据结束,theResource = {0}", theResource);
//释放读操作锁
rwl.ReleaseReaderLock();
}
catch (ApplicationException)
{
//获取读操作锁失败的处理
}
}
}
//写数据
static void Write()
{
for (int i = 0; i < 3; i++)
{
try
{
//申请写操作锁,如果在1000ms内未获取写操作锁,则放弃
rwl.AcquireWriterLock(1000);
Console.WriteLine("开始写数据,theResource = {0}", theResource);
//将theResource加1
theResource++;
Thread.Sleep(100);
Console.WriteLine("写数据结束,theResource = {0}", theResource);
//释放写操作锁
rwl.ReleaseWriterLock();
}
catch (ApplicationException)
{
//获取写操作锁失败
}
}
}
}
}
并行问题
** .NET 4.0 引入 C# TPL(Task Parallel Library)** TPL并行库 System.Threading.Tasks; 构建细粒度的、可扩展的并行代码,而不必直接与线程和线程池打交道。
0. System.Threading.Tasks 命名空间的成员
1.Task 继承关系
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Security;
using System.Security.Permissions;
namespace System.Threading.Tasks
{
[DebuggerTypeProxy(typeof(SystemThreadingTasks_TaskDebugView))]
[DebuggerDisplay("Id = {Id}, Status = {Status}, Method = {DebuggerDisplayMethodDescription}")]
[__DynamicallyInvokable]
[HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable
{
internal class ContingentProperties
{
internal ExecutionContext m_capturedContext;
internal volatile ManualResetEventSlim m_completionEvent;
internal volatile TaskExceptionHolder m_exceptionsHolder;
internal CancellationToken m_cancellationToken;
internal Shared<CancellationTokenRegistration> m_cancellationRegistration;
internal volatile int m_internalCancellationRequested;
internal volatile int m_completionCountdown = 1;
internal volatile List<Task> m_exceptionalChildren;
internal void SetCompleted()
{
m_completionEvent?.Set();
}
internal void DeregisterCancellationCallback()
{
if (m_cancellationRegistration != null)
{
try
{
m_cancellationRegistration.Value.Dispose();
}
catch (ObjectDisposedException)
{
}
m_cancellationRegistration = null;
}
}
}
2.IThreadPoolWorkItem
using System.Security;
namespace System.Threading
{
internal interface IThreadPoolWorkItem
{
[SecurityCritical]
void ExecuteWorkItem();
[SecurityCritical]
void MarkAborted(ThreadAbortException tae);
}
}
#if false // 反编译日志
缓存中的 11 项
#endif
[__DynamicallyInvokable]
public static TaskFactory Factory
{
[__DynamicallyInvokable]
get
{
return s_factory;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task StartNew(Action action)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task.InternalStartNew(internalCurrent, action, null, m_defaultCancellationToken, GetDefaultScheduler(internalCurrent), m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task StartNew(Action action, CancellationToken cancellationToken)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task.InternalStartNew(internalCurrent, action, null, cancellationToken, GetDefaultScheduler(internalCurrent), m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task StartNew(Action action, TaskCreationOptions creationOptions)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task.InternalStartNew(internalCurrent, action, null, m_defaultCancellationToken, GetDefaultScheduler(internalCurrent), creationOptions, InternalTaskOptions.None, ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task StartNew(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
return Task.InternalStartNew(Task.InternalCurrentIfAttached(creationOptions), action, null, cancellationToken, scheduler, creationOptions, InternalTaskOptions.None, ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal Task StartNew(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
return Task.InternalStartNew(Task.InternalCurrentIfAttached(creationOptions), action, null, cancellationToken, scheduler, creationOptions, internalOptions, ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task StartNew(Action<object> action, object state)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task.InternalStartNew(internalCurrent, action, state, m_defaultCancellationToken, GetDefaultScheduler(internalCurrent), m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task StartNew(Action<object> action, object state, CancellationToken cancellationToken)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task.InternalStartNew(internalCurrent, action, state, cancellationToken, GetDefaultScheduler(internalCurrent), m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task StartNew(Action<object> action, object state, TaskCreationOptions creationOptions)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task.InternalStartNew(internalCurrent, action, state, m_defaultCancellationToken, GetDefaultScheduler(internalCurrent), creationOptions, InternalTaskOptions.None, ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task StartNew(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
return Task.InternalStartNew(Task.InternalCurrentIfAttached(creationOptions), action, state, cancellationToken, scheduler, creationOptions, InternalTaskOptions.None, ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task<TResult> StartNew<TResult>(Func<TResult> function)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task<TResult>.StartNew(internalCurrent, function, m_defaultCancellationToken, m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(internalCurrent), ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task<TResult> StartNew<TResult>(Func<TResult> function, CancellationToken cancellationToken)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task<TResult>.StartNew(internalCurrent, function, cancellationToken, m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(internalCurrent), ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task<TResult> StartNew<TResult>(Func<TResult> function, TaskCreationOptions creationOptions)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task<TResult>.StartNew(internalCurrent, function, m_defaultCancellationToken, creationOptions, InternalTaskOptions.None, GetDefaultScheduler(internalCurrent), ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task<TResult> StartNew<TResult>(Func<TResult> function, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
return Task<TResult>.StartNew(Task.InternalCurrentIfAttached(creationOptions), function, cancellationToken, creationOptions, InternalTaskOptions.None, scheduler, ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task<TResult> StartNew<TResult>(Func<object, TResult> function, object state)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task<TResult>.StartNew(internalCurrent, function, state, m_defaultCancellationToken, m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(internalCurrent), ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task<TResult> StartNew<TResult>(Func<object, TResult> function, object state, CancellationToken cancellationToken)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task<TResult>.StartNew(internalCurrent, function, state, cancellationToken, m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(internalCurrent), ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task<TResult> StartNew<TResult>(Func<object, TResult> function, object state, TaskCreationOptions creationOptions)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Task internalCurrent = Task.InternalCurrent;
return Task<TResult>.StartNew(internalCurrent, function, state, m_defaultCancellationToken, creationOptions, InternalTaskOptions.None, GetDefaultScheduler(internalCurrent), ref stackMark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
[__DynamicallyInvokable]
public Task<TResult> StartNew<TResult>(Func<object, TResult> function, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)
{
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
return Task<TResult>.StartNew(Task.InternalCurrentIfAttached(creationOptions), function, state, cancellationToken, creationOptions, InternalTaskOptions.None, scheduler, ref stackMark);
}
DataParallelismWithForEach
并行处理图片的案例
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
// Need these namespaces!
using System.Threading.Tasks;
using System.Threading;
using System.IO;
namespace DataParallelismWithForEach
{
public partial class MainForm : Form
{
// New Form level variable.
private CancellationTokenSource cancelToken = new CancellationTokenSource();
public MainForm()
{
InitializeComponent();
}
private void btnProcessImages_Click(object sender, EventArgs e)
{
// Start a new "task" to process the files. 启动一个新的“任务”来处理文件。
Task.Factory.StartNew(() =>
{
ProcessFiles();
});
}
private void ProcessFiles()
{
// Use ParallelOptions instance to store the CancellationToken 并行选项 取消令牌 [ˈpærəlel]
ParallelOptions parOpts = new ParallelOptions();
parOpts.CancellationToken = cancelToken.Token;
parOpts.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
// Load up all *.jpg files, and make a new folder for the modified data.
string[] files = Directory.GetFiles(@"F:\Pictures", "*.jpg",
SearchOption.AllDirectories);
string newDir = @"F:\ModifiedPictures";
Directory.CreateDirectory(newDir);
try
{
// Process the image data in a parallel manner!
Parallel.ForEach(files, parOpts, currentFile =>
{
parOpts.CancellationToken.ThrowIfCancellationRequested();
string filename = Path.GetFileName(currentFile);
using (Bitmap bitmap = new Bitmap(currentFile))
{
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
bitmap.Save(Path.Combine(newDir, filename));
//this.Text = string.Format("Processing {0} on thread {1}", filename,
// Thread.CurrentThread.ManagedThreadId);
// We need to ensure that the secondary threads access controls
// created on primary thread in a safe manner.
this.Invoke((Action)delegate
{
this.Text = string.Format("Processing {0} on thread {1}", filename, Thread.CurrentThread.ManagedThreadId);
});
}
}
);
}
catch (OperationCanceledException ex)
{
this.Invoke((Action)delegate
{
this.Text = ex.Message;
});
}
}
private void btnCancelTask_Click(object sender, EventArgs e)
{
// This will be used to tell all the worker threads to stop!这将用于通知所有工作线程停止!
cancelToken.Cancel();
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Net;
namespace MyEBookReader
{
public partial class MainForm : Form
{
private string theEBook = "";
public MainForm()
{
InitializeComponent();
}
private void btnDownload_Click(object sender, EventArgs e)
{
// The Project Gutenberg EBook of A Tale of Two Cities, by Charles Dickens
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (s, eArgs) =>
{
theEBook = eArgs.Result;
txtBook.Text = theEBook;
};
wc.DownloadStringAsync(new Uri("http://www.gutenberg.org/files/98/98-8.txt"));
}
#region Do work in parallel!
private void btnGetStats_Click(object sender, EventArgs e)
{
// Get the words from the e-book.
string[] words = theEBook.Split(new char[] { ' ', '\u000A', ',', '.', ';', ':', '-', '?', '/' },
StringSplitOptions.RemoveEmptyEntries);
string[] tenMostCommon = null;
string longestWord = string.Empty;
Parallel.Invoke(
() =>
{
// Now, find the ten most common words.
tenMostCommon = FindTenMostCommon(words);
},
() =>
{
// Get the longest word.
longestWord = FindLongestWord(words);
});
// Now that all tasks are complete, build a string to show all 现在所有任务都完成了
// stats in a message box.
StringBuilder bookStats = new StringBuilder("Ten Most Common Words are:\n");
foreach (string s in tenMostCommon)
{
bookStats.AppendLine(s);
}
bookStats.AppendFormat("Longest word is: {0}", longestWord);
bookStats.AppendLine();
MessageBox.Show(bookStats.ToString(), "Book info");
}
#endregion
#region Task methods.
private string[] FindTenMostCommon(string[] words)
{
var frequencyOrder = from word in words
where word.Length > 6
group word by word into g
orderby g.Count() descending
select g.Key;
string[] commonWords = (frequencyOrder.Take(10)).ToArray();
return commonWords;
}
private string FindLongestWord(string[] words)
{
return (from w in words orderby w.Length descending select w).FirstOrDefault();
}
#endregion
}
}
3.IAsyncResult
.NET 4.5 下的异步问题
多线程的异常处理
(一) 异常处理的几个基本原则
1、 基本原则:不要轻易捕获根异常;
2、 组件或控件抛出异常时可以根据需要自定义一些异常,不要抛出根异常,可以直接使用的常用异常有:FormatException、IndexOutOfRangException、InvalidOperationException、InvalidEnumArgumentException ;没有合适的就自定义;
3、 用户自定义异常从ApplicationException继承;
4、 多线程的内部异常不会传播到主线程,应该在内部进行处理,可以通过事件推到主线程来;
5、应用程序层面可以捕获根异常,做一些记录工作,切不可隐匿异常。
(二) 异常处理方案(基于WPF实现)
捕获你知道的异常,并自行处理,但不要轻易捕获根异常,下面的代码令人深恶痛绝:
try
{
DoSomething();
}
catch(Exception)
{
//Do Nothing
}
当然,如果你确定有能力捕获根异常,并且是业务逻辑的一部分,可以捕获根异常 :
try
{
DoSomething();
MessageBox.Show("OK");
}
catch(Exception ex)
{
MessageBox.Show($"ERROR:{ex.Message}");
}
可等待异步任务的异常处理:
可等待的任务内的异常是可以传递到调用者线程的,可以按照主线程异常统一处理:
try
{
await DoSomething();
}
catch(FormatException ex)
{
//Do Something
}
Task任务内部异常处理:
非可等待的Task任务内部异常是无法传递到调用者线程的,参考下面代码:
try
{
Task.Run(() =>
{
string s = "aaa";
int i = int.Parse(s);
});
}
catch (FormatException ex)
{
MessageBox.Show("Error");
}
上面代码不会实现你期望的效果,它只会造成程序的崩溃。(有时候不会立即崩溃,后面会有解释)
处理办法有两个:
1、自行处理:
(1)处理可以预料的异常,(2)同时处理根异常(写日志等),也可以不处理根异常,后面统一处理;
2、或将异常包装成事件推送到主线程,交给主线程处理。
Thread和ThreadPool内部异常:
虽然不推荐使用Thread,如果实在要用,其处理原则和上述普通Task任务内部异常处理方案一致。
全局未处理异常的处理:
虽然我们不推荐catch根异常,但如果一旦发生未知异常程序就崩溃,客户恐怕难以接受吧,如果要求所有业务模块都处理根异常并进行保存日志、弹出消息等操作又非常繁琐,所以,处理的思路是业务模块不处理根异常,但应用程序要对未处理异常进行统一处理。
public partial class App : Application
{
App()
{
this.Startup += App_Startup;
}
private void App_Startup(object sender, StartupEventArgs e)
{
this.DispatcherUnhandledException += App_DispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}
//主线程未处理异常
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
DoSomething(e.Exception);
e.Handled = true;
}
//未处理线程异常(如果主线程未处理异常已经处理,该异常不会触发)
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
if (e.ExceptionObject is Exception ex)
{
DoSomething(ex);
}
}
//未处理的Task内异常
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
DoSomething(e.Exception);
}
//保存、显示异常信息
private void ProcessException(Exception exception)
{
//保存日志
//提醒用户
}
}
解释一下:
1、 当主线程发生未处理异常时会触发App_DispatcherUnhandledException事件,在该事件中如果设置e.Handled = true,那么系统不会崩溃,如果没有设置e.Handled = true,会继续触发CurrentDomain_UnhandledException事件(毕竟主线程也是线程),而CurrentDomain_UnhandledException事件和TaskScheduler_UnobservedTaskException事件触发后,操作系统都会强行关闭这个应用程序。所以我们应该在App_DispatcherUnhandledException事件中设置e.Handled = true。
2、Thread线程异常会触发CurrentDomain_UnhandledException事件,导致系统崩溃,所以建议尽量不要使用Thread和ThreadPool。
3、非可等待的Task内部异常会触发TaskScheduler_UnobservedTaskException事件,导致系统崩溃,所以建议Task内部自行处理根异常或将异常封装为事件推到主线程。需要额外注意一点:Task内的未处理异常不会被立即触发事件,而是要延迟到GC执行回收的时候才触发,这使得问题更复杂,需要小心处理。