Open rainit2006 opened 7 years ago
Thread,waithandle http://www.cnblogs.com/JimmyZheng/archive/2012/06/10/2543143.html
Thread的重要属性: 1 CurrentThread 获取到当前线程的对象 2 IsAlive 判断线程是否处于激活状态 3 IsBackground 设置该线程是否是后台线程,一旦设置true 的话,该线程就被标示为后台线程。再次强调下后台线程的终止不会导致进程的终止 4 IsThreadPoolThread 只读属性标示该线程是否属于线程池的托管线程,一般我通过线程池创建的线程该属性都是true 5 Name 获取到线程的名字,我们可以根据业务或者逻辑来自定义线程的名字 6 Priority 这个属性表示线程的优先级,我们可以用ThreadPriority这个枚举来设置这个属性 7, ManagedThreadId Thread ID
关于Thread的sleep
EventWaitHandle EventWaithandle are the events, which signals and releases one or more waiting threads and once the threads are released, EventWaitHandle is reset; either automatically or manually.
This is implemented by using the members of class given below. -- WaitOne() - Blocks the current thread until a current WaitHandle receives the signal. -- Set() - Sets the state of the event to be signaled, unblocking one or more waiting threads to execute. -- Reset() - Resets the state of the event to non-signaled to block the threads.
AutoResetEvent:
Event Wait Handles, which resets automatically
The threads are blocked by calling WaitOne() method, followed by notifying the thread; which is notified or signaled by calling Set() method. As the Set() method is called, one of the waiting thread is released and AutoResetEvent automatically calls Reset() method to reset WaitHandle and returns it to non-signaled state.
You can create an AutoResetEvent in two ways,
static EventWaitHandle _waitHandle = new EventWaitHandle(false,EventResetMode.AutoReset);
OR
static EventWaitHandle _waitHandle = new AutoResetEvent(false);
class AutoResetEventClass
{
private static AutoResetEvent AutoEvent = new AutoResetEvent(false);
static void Main()
{
for (int i = 0; i <= 2; i++)
{
Thread t = new Thread(ThreadProc);
t.Name = "Thread_" + i;
t.Start();
}
Thread.Sleep(500);
Console.WriteLine("Press Enter to signal the waiting threads");
Console.ReadLine();
AutoEvent.Set();
Thread.Sleep(500);
Console.ReadLine();
}
private static void ThreadProc()
{
string name = Thread.CurrentThread.Name;
Console.WriteLine(name + " starts and calls WaitOne()");
AutoEvent.WaitOne();
Console.WriteLine(name + " Realeased.");
Thread.Sleep(500);
}
}
因为AutoEvent在set后只允许一个thread通过(通过一个Thread后自动reset将其他Thread给lock住),所以从结果图里可见:只有Thread2被释放了。
ManualResetEvent: Event Wait Handles that resets manually
CountdownEvent This event unblocks the waiting threads after receiving a certain number of signals. 可参考下面的说明例子: http://www.c-sharpcorner.com/article/thread-synchronization-signaling-constructs-with-eventwaithandle-in-c-sharp/
Thread 的 join 方法 继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程,直到某个线程终止为止。
这里,“调用线程”指调用joint的线程。 下面例子里,MainThread调用了线程t的joint,所以MainThread要等待t执行完才会显示出“join thread”语句。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
// スレッドの作成
Thread t = new Thread(new ThreadStart(TestThread));
t.Start(); // スレッドの開始
// メインスレッドが終了した場合に、動作中のスレッドも終了させる場合
//t.IsBackground = True
// スレッドの優先度の変更
//t.Priority = ThreadPriority.Lowest;
// スレッドの強制終了させる場合
//t.Abort();
Console.WriteLine("wait thread.");
// スレッドの終了を待ち合わせる
t.Join();
Console.WriteLine("join thread");
Console.ReadKey();
}
///<summary>
/// スレッドとして動作するメソッド
/// </summary>
static void TestThread()
{
Console.WriteLine("sleep...");
// 3秒間停止
Thread.Sleep(3000);
Console.WriteLine("wake up. thread end.");
}
}
}
CLR线程池 创建线程的开销是很大的,在需要以性能为重的项目中这的确容易导致一些性能问题。
线程池就是一个帮助我们开发人员实现多线程的一个方案,就是用来存放“线程”的对象池,利用线程池我们可以开发出性能比较高的对于多线程的应用,同时减低一些不必要的性能损耗,我们不必去手动创建线程,线程池根据给定线程池中的任务队列的队列速度和相关任务执行速度相比较去自己添加或复用线程。 好处: 1 尽量少的创建线程并且能将线程反复利用 2 最好不要销毁而是挂起线程达到避免性能损失 3 通过一个技术达到让应用程序一个个执行工作,类似于一个队列 4 如果某一线程长时间挂起而不工作的话,需要彻底销毁并且释放资源 5 如果线程不够用的话能够创建线程,并且用户可以自己定制最大线程创建的数量
ThreadPool.QueueUserWorkItem: スレッドプールに登録
异步线程 调用者发送一个请求 -> 调用者去做自己的事情 -> 请求会异步执行 -> 执行完毕可以利用回调函数告诉调用者(也可以不用)
AsyncCallback 委托(delegate) References a method to be called when a corresponding asynchronous operation completes.
[SerializableAttribute]
[ComVisibleAttribute(true)]
public delegate void AsyncCallback(
IAsyncResult ar //The result of the asynchronous operation.
)
其实这个委托是微软给我们提供的用于异步执行方法体后通知该异步方法已经完成。AsyncCallBack抽象了所有异步方法执行后回调函数(方法),它规定了回调函数(方法)必须拥有一个IAsyncResult的参数并且没有返回值.
BeginInvoke and EndInvoke The methods BeginInvoke and EndInvoke will do all the work for you. BeginInvoke will process your asychronous call in a new thread. EndInvoke will return you the result from that thread. If the thread is not completed when you are calling EndInvoke, it will wait. You don't have to implement your wait loop in the main thread. The status of each asynchronous operations are saved in the IAsynicResult object, so one delegate can handle multiple invokes. デリゲート型を定義すると、 C# コンパイラによって自動的に BeginInvoke と EndInvoke というメソッドが生成されます。 この BeginInvoke を用いることにより非同期呼び出しを開始し、 EndInvoke を用いることにより非同期処理の終了を待つ事が出来ます。
BeginInvoke の引数は、デリゲート型の定義時に引数リストで指定した引数と、System.AsyncCallback デリゲート型の引数および object 型の引数をとり、System.IAsyncResult インターフェース型の値を返します。
BeginInvoke によるデリゲートの非同期呼び出しは、 内部的には「スレッド プール」を使っています。
例えば:
public delegate void ShowMessage(int n); //只有一个int型引数
IAsyncResult ar = asyncCall.BeginInvoke(N, null, null); //第一个引数则是int型。第二个是AsynCallback型
说明得比较清楚的网页: http://ufcpp.net/study/csharp/sp_delegate.html
ラムダ式 C# 3.0 では、匿名関数をさらに簡便な記法で書けるようになりました。
C# 2.0 の記法では、以下のように書いていたものを、 delegate(int n){ return n > 10; }
3.0 では以下のように書けるようになりました。 (int n) => { return n > 10; }
変数の型が左辺値や関数の引数から推論できる場合にはさらに簡素化できて、以下のように書けます。 Func<int, bool> f = n => { return n > 10; };
また、ラムダ式の中身が return 文1つだけの場合には、{} や return も省略できて、 以下のように書けます。 Func<int, bool> f = n => n > 10;
このような記法をラムダ式(lambda expression)と呼びます。 ラムダ式は、実際には、「匿名関数としても使えるもの」で、 匿名メソッド式(匿名関数としてしか使えない)よりも用途が広いです。
异步形式: Classic Async Pattern 和Event-based Async Pattern Classic Async Pattern: 其实Classic Async Pattern指的就是我们常见的BeginXXX和EndXXX IAsyncResult 异步设计模式通过名为 BeginOperationName 和 EndOperationName 的两个方法来实现原同步方法的异步调用。让我们再来回顾下.net中的几个的BeginXXX 和EndXXX
Stream中的BeginRead,EndRead,BeginWrite,EndWrite
Socket中的BeginReceive,EndReceive
HttpWebRequest的BeginGetRequestStream和EndGetRequestStream....
Event-based Async Pattern
Event-based Async Pattern 值的是类似于 xxxxxxxAsync() 和 类似于event xxxxxCompleteHander
通过一个方法和一个完成事件来处理异步操作
.net中的例子
WebClient.DownloadStringAsync(string uri)和 event DownloadStringCompleteEventHandler
其实Classic Async Pattern和Event-based Async Pattern都是一种异步的设计思路,我们也可以根据这一系列的思路去实现自己的异步方法
.net4.5中异步的简化: async,await net4.5之前存在的问题:往往回调方法和普通方法会搞错,在复杂的项目面前,有时候简直无法维护,到处都是回调函数,眼花缭乱。
微软为了简化异步的实现过程,甚至大刀阔斧将回调函数做成看起来像同步方法。
/// <summary>
/// .net 4.5 中 async 和 await 全新的关键字 一起实现异步的简化
/// </summary>
void async ShowUriContent(string uri)
{
using (FileStream fs = File.OpenRead("你的文件地址"))
{
using (FileStream fs2 = new FileStream())
{
byte[] buffer = new byte[4096];
//FileStream的ReadAsync方法也是net4.5版本出现的,它返回一个Task<int>对象
//而且作用于await后的异步代码会等待阻塞直到异步方法完成后返回
int fileBytesLength = await fs.ReadAsync(buffer,0,buffer.Length).ConfigureAwait(false);
while(fileBytesLength>0)
{
//FileStream的WriteAsync方法也是net4.5版本出现的
await fs2.WriteAsync(buffer,0,buffer.Length).ConfigureAwait(false);
}
}
}
}
更简单的例子:
private async void btnDoStuff_Click(object sender, RoutedEventArgs e)
{
await Task.Delay(4000);
Console.writeline("do something");
}
■Taskのwait と await t.Wait();
wait for t: 類義語はstay(とどまる)
スレッドを止めて待つ
await t;
await t: 類義語はextpect(期待する)
スレッドを止めずにコールバックを待つ
だから、非同期処理の進捗をプログレスバーに表示したいときUIスレッドでTask.Waitしちゃうとうまくいかないです。 というわけで、Task.Waitはあまり使わない方がいいという話。
异步处理的历史: http://blog.xin9le.net/entry/2012/07/12/005839
-Thread -ThreadPool -Event-based Asynchronous Pattern Windows FormsやWeb Formsが全盛期だった頃に出てきた、非同期処理の完了イベントなどを発生させるイベント主体の記述方法です。
-Task-based Asynchronous Pattern .NET Framework 4で登場したTask Parallel Libraryの要であるTaskクラスを利用した方法です。Taskクラスとは「何かの処理」を抽象化したものと考えれば良いかと思います。ContinueWithメソッドで「作業が終了したら続けてコレを実行してください」と記述しています。引数のTaskScheduler.FromCurrentSynchronizationContextメソッドにより、「続けて行う処理はUIに同期的に実行してください」と指示しています。これまでの書き方に比べて、非同期処理の意図をかなり分かりやすく表現しているのではないかと思います。
private void Button_Click(object sender, RoutedEventArgs e)
{
this.button.IsEnabled = false;
Task.Factory.StartNew(() => Thread.Sleep(3000))
.ContinueWith(_ =>
{
this.button.IsEnabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
-Reactive Extensions: イベントや非同期処理をLINQの形で記述する方法です。 。.NET Framework 3.5から利用できるので、async/awaitが利用できない環境下では最善の実装方法と言えるのではないかと思います。
-async/await : .NET Framework 4.5 / C# 5.0から提供される
选择我们需要的同步工具: lock, mutex,semaphore, EventWaitHandle 有时我们苦于在项目中寻找适合的同步工具,所以一下仅是个人观点,大伙可以讨论或者发表自己的意见 1 处于性能要求考虑:可以考虑用基元用户模式的同步工具,也就是前一篇中的一些同步工具,尽量不要考虑mutex因为其功能强大所以性能损失太多 2 处于功能考虑:如果项目中牵涉到复杂的同步而且不需要严格的性能要求,例如跨进程,混合锁或者递归锁等等,则最好选择基元内核模式中的同步工具 3 分布式开发,在各个服务间实现同步的话,当然mutex是第一考虑 4 中小型项目大家可以随意,根据业务需要决定
lock : Ensures just one thread can access a resource, or section of code. 单进程内 Mutex : Ensures just one thread can access a resource, or section of code. Can be used to prevent multiple instances of an application from starting. 支持多进程 Semaphore : Ensures not more than a specified number of threads can access a resource, or section of code. 支持多数thread同时访问 EventWaitHandle: 表现为线程的同步信号。
EventWaitHandleSecurity 可以实现cross-process的同步。
An EventWaitHandleSecurity object specifies access rights for a named system wait handle, and also specifies the way access attempts are audited. Access rights to the wait handle are expressed as rules, with each access rule represented by an EventWaitHandleAccessRule object. Each auditing rule is represented by an EventWaitHandleAuditRule object.
然后结合EventWaitHandle使用: EventWaitHandle(Boolean, EventResetMode, String, Boolean, EventWaitHandleSecurity)
Action
DispatherによるUI更新 (for WPF) 別スレッドによるUI更新は、invokeやpostmessageを使った更新遅延テクニックを使いますが、WPFで標準的に利用されているDispacherクラスを利用したUI更新を使うことができます。むしろ、今後はこの更新テクニックが主流であるといえます。Postmessageは役割を終えたのかもしれません。
使い方の例。Dispacherオブジェクトを格納する変数をForm_Loadで格納しておきます。
private System.Windows.Threading.Dispatcher _dispather = null;
private void MainForm_Load(object sender, EventArgs e)
{
// UIスレッドのディスパッチャー取得
_dispather = Dispatcher.CurrentDispatcher;
}
あとは、UIを更新するところで以下のように記述します。
_dispather.BeginInvoke(new Action(() => {
this.checkBox1.Checked = false;
}), null);
Dispatcher.InvokeとThread.startは違います。
Dispatcher.Invokeはスレッドを実行するというのではなく、MSDNの説明を借りると、” Dispatcher が関連付けられているスレッドで、指定した Action を同期的に実行します。”という物です。
さっぱりですね、この説明。
”Dispatcher が関連付けられているスレッド”ってなんでしょうか?
実はWPFのコントロールってほとんどの物が、System.Windows.Threading.DispatcherObjectというクラスから派生しています。
例えば、TextBoxの以下のページをご覧ください。 https://msdn.microsoft.com/ja-jp/library/system.windows.controls.te...
上の方に”継承階層”というのがありますが、上の方にDispatcherObjectクラスがいますよね?
ではそのDispatcherObjectの所のリンクを開くと、説明に
” Dispatcher に関連付けられているオブジェクトを表します。”
と書いてます。
つまり、”Dispatcher が関連付けられているスレッド”というのはコントロールなどのGUIと紐づいているUIスレッドの事です。
さらにいうと、何も考えずにコードを記述すると実行されるのはこのUIスレッドです。
さて、前置きが長くなりましたが、
” Dispatcher が関連付けられているスレッドで、指定した Action を同期的に実行します。”
をもう一度見てみると…つまり、UIスレッドで指定したActionを同期的に実行します。という事ですね。
例を出すと、Taskクラスを使って
Task.Factory.StartNew(() => { textBox1.Text = "Sample"; });
とスレッドを使って実行するとします。
でも、これは例外が出ます。
理由は、UIスレッド以外からUIスレッドにアクセスしているからです。
では、どうすればよいのでしょうか?
という事で登場したのがDispatcher.Invokeです。
以下のように書き換えます。
Task.Factory.StartNew(() => { Dispatcher.Invoke(new Action(() => { textBox1.Text = "Sample"; })); });
これで例外が出ません。
Dispatcher.Invokeの部分はUIスレッドで同期的に実行するからです。 (同期的って難しく感じますが、UIスレッドで普通に実行されるという意味で構いません)
ということで、Dispatcher.InvokeはThread.startのようにスレッドを実行するというのではなく、UIスレッド上で実行してね!と別スレッドからUIスレッドに対してお願いするものです。
なんでスレッド関連のクラスっていっぱいあるの?と思いますが、実はThreadはFramework2.0とかでも使えますが、TaskはFramework4.0で追加された物です。 そんな感じで、後で追加され、その分簡単に記述できるようになっていたりします。
アプリ開発時にターゲットにするFrameworkや手軽さも含めて使いやすいやつを使えばよいと思います。
Dispatcher Provides services for managing the queue of work items for a thread.
Invoke(Action) :Executes the specified Action synchronously on the thread the Dispatcher is associated with.
The Dispatcher maintains a prioritized queue of work items for a specific thread.When a Dispatcher is created on a thread, it becomes the only Dispatcher that can be associated with the thread, even if the Dispatcher is shut down. In WPF, a DispatcherObject can only be accessed by the Dispatcher it is associated with. For example, a background thread cannot update the contents of a Button that is associated with the Dispatcher on the UI thread. In order for the background thread to access the Content property of the Button, the background thread must delegate the work to the Dispatcher associated with the UI thread. This is accomplished by using either Invoke or BeginInvoke. Invoke is synchronous and BeginInvoke is asynchronous. The operation is added to the queue of the Dispatcher at the specified DispatcherPriority.
http://d.hatena.ne.jp/hilapon/20130225/1361779314 WPF アプリケーションは、レンダリング(描画)用とユーザーインターフェイス用の2つのスレッドを用いて動作します。 レンダリングスレッドはバックグラウンド動作のため、開発者はアクセスできません。開発者が扱えるのは、ユーザーインターフェイス用スレッド(以下 UIスレッド)のみとなります。そしてこの UI スレッドは、パフォーマンス上の理由により(スレッドセーフにするとオーバーヘッドが発生し、描画遅延等の問題を起こすためか)スレッドセーフに設計されてないのが特徴です。 WPFでは、ほとんどのオブジェクトが UI スレッド上で動作する「シングルスレッドモデル」を採用しているため、UI スレッド外からそのオブジェクトにアクセスすると、例外が発生します。
Dispatcher を使う場面は View だけとは限りません。Application.Current.Dispatcher を使い ViewModel の通知プロパティやコマンドの CanExecute メソッド内で使うことも可能です。
TPL、Task
.NET Framework 4.0 以降 (Visual Studio 2010 以降) になると, Taskが出てきた。 https://code.msdn.microsoft.com/windowsdesktop/32-Parallel-af4950ae
10 行でズバリ!! 並列プログラミング - TPL によるタスクの並列処理 (C#) https://code.msdn.microsoft.com/windowsdesktop/10-TPL-C-7b3c4f89
Taskクラス タスクの作成では、必ずしも、個々のタスクについてそれぞれスレッドが作成されるわけではなく、.NET Framework が持つタスク スケジューラにより、各タスクと使用するスレッドが管理されています。
static void Main(string[] args)
{
var task = Task.Factory.StartNew( ()=>
{
for( int i=0; i<9 ; i++ )
{
Console.Write(i);
Thread.Sleep(500);
}
});
var continueTask = task.ContinueWith((t) =>
{
for (int i = 10; i < 19; i++)
{
Console.Write(i);
Thread.Sleep(500);
}
});
continueTask.Wait();
Console.WriteLine("done");
実行結果: 012345678101112131415161718done
Parallelクラス TPL の最も基本的な機能は Parallel クラスで提供されており、ここでは Parallel クラスの提供する Invoke() メソッドを使用して、 2 つのメソッド呼び出しを並列化しています。
実行すべき処理が複数あるとき: Parallel.Invoke メソッドを使用
static void Main(string[] args)
{
Parallel.Invoke(
() =>
{
for (int i = 0; i < 9; i++)
{
Console.Write("{0} ", i);
Thread.Sleep(500);
}
},
() =>
{
Thread.Sleep(250);
for (int i = 10; i < 19; i++)
{
Console.Write("{0} ", i);
Thread.Sleep(500);
}
});
Console.WriteLine("done");
}
実行結果:
0 10 1 11 2 12 3 13 4 14 5 15 6 16 7 17 8 18 done
Parallel.Invoke() はパラメーターとして複数の Action デリゲート (すなわち戻り値の無いメソッド) を指定することができ、Parallel.Invoke() に渡された Action デリゲートは自動的に並列に処理され呼び出されます。もしも実行したいメソッドに引数を渡して呼び出したい場合は、以下のようにラムダ式を記述して呼び出すことができます。
Parallel.Invoke(
() => ExpensiveTaskWirhParam1(1000),
() => ExpensiveTaskWithParam2("Param", DataTime.Now));
TPL の例外処理
TPL では処理が並列的に実行されるため、同時に複数の例外が生じる可能性があります。並列処理中に例外が発生した場合、TPL は実行中の他の並列処理を停止することを試みます。しかし、その間に他の例外が生じる可能性があります。この問題に対処するため、新たな例外として System.AggregateException 例外クラスが実装されました。TPL による並列処理が実行されるコード ブロック (例えば Parallel.Invoke() の呼び出し) を AggregateException 例外クラスを捕捉する try ~ catch コードを記述することで、TPL で並列処理中に発生した例外を捕捉することが可能になっています。
try { // TPL でタスクを並列処理化 Parallel.Invoke(ExpansiveProcess1, ExpensiveProcess2); } catch (AggregateException aex) // AggregateException を捕捉 { foreach (var ex in aex.InnerExceptions) // TPL 処理内で発生した例外を列挙 { // ここに捕捉した例外を処理するコードを記述 } }
- Invoke メソッドの引数に、同じメソッド名を複数回に実行する場合:Parallel.For メソッドを使用
private static void method(int i) { Thread.Sleep(300-i*100); Console.WriteLine("私は{0}です。", i); }
static void Main(string[] args)
{
Parallel.For(0, 3, method);
}
実行結果:
私は2です。
私は1です。
私は0です。
- 並列処理できるコレクション: Parallel.ForEach
static void Main(string[] args)
{
var list = new List
Parallel.ForEach(list, (val) =>
{
Thread.Sleep(('W'-val[0]) * 10);
Console.WriteLine(val);
});
}
実行結果:
Woo
Foo
Boo
- 普通に foreach 文だと、通常は 1 つの処理で実行されるが、Parallel クラスなどを使えば複数CPUのコアもパワー全開できる
- Parallel ループから抜け出る
ループの途中で特定の条件に達したとき、ループを抜け出したい場合があります。このような場合は、ParallelLoopState オブジェクトを指定する構文を利用します。以下は部分的なコードですが、こんな感じです。
int N = 10000; Parallel.For(0, N, (n, loopState) => { ....
if (条件) { loopState.Break(); return; } });
- Parallel ループを中止する
前項の「Parallel ループから抜け出る」とほぼ同じですが、Break メソッドのかわりに Stop メソッドを使います。
- 并列方法想有返回值怎么办?
比如:
public class Work { public static void Main() {
Parallel.Invoke(() => DoWork("Raju"),
() => DoWork("Ramu"));
}
public static string DoWork(string data)
{
return "tesing" + data;
}}
In above method I want get DoWork return value.
Just handle the return value like this:
string result1, result2;
Parallel.Invoke(
() => result1 = DoWork("Raju"),
() => result2 = DoWork("Ramu"));
Also remember that whenever you do something in parallel you need to be careful to avoid data races and race conditions.
线程模式(STA, MTA)
接下去就好理解了,一个小城镇(进程)里可以有很多很多的(居民)线程, 这个城镇(进程)只有一间旅馆(MTA),但可以有很多很多的居民房(STA). 只有居民(线程)进入了房间(居民房或旅馆,STA或MTA)以后才能使用该房间里的物品(COM对象), 居民房(STA)里的物品(COM对象)只能供这间房子的主人(创建该STA的线程)使用,其它居民(线程)不能访问. 同样,只有入住到旅馆(MTA)里的居民(线程,可以有多个)才可以访问到旅馆(MTA)里的物品(com对象),但因为是公用的,所以要合理的分配(同步)才能不会产生混乱.
-.NET支持两种线程模型:STA和MTA。 STA(single threaded apartments)。apartment只是一个逻辑上的概念,它可以包含一个或多个线程。一个AppDomain可以包括一个或多个apartment。
STA是指该apartment中只能包含一个thread。 MTA(multi threaded apartments)。指该apartment中可以包含多个thread。 STA and MTA 之间最大的区别就是MTA 可以在同一个apartment 中使用所有的共享资源并发执行多个线程。 而多个STA虽然可以共享数据,但是不能并发执行线程,存在性能问题。
-任何一个WPF程序,Main方法前必须有一个[STAThread]特性,否则编译会失败。这个特性用来申明该应用程序的初始线程模型为STA,以便和COM兼容。STA是.NET之前,旧的COM年代的词汇。
STA: Single-Thread Apartment, 中文叫单线程套间。就是在COM库初始化的时候创建一个内存结构,然后让它和调用CoInitialize的线程相关联。这个内存结构针对每个线程都会有一个。支持STA的COM对象只能在创建它的线程里被使用,其它线程如果再创建它就会失败。
MTA: Mutil-Thread Apartment,中文叫多线程套间。COM库在进程中创建一个内存结构,这个内存结构在整个进程中只能有一个,然后让它和调用CoInitializeEx的线程相关联。支持MTA的COM对象可以在任意线程里被使用。多有针对它的调用都会被封装成为消息。
其实STA和MTA是COM规定的一套线程模型,用于保障多线程情况下你的组件代码的同步。比如说有一个COM对象它内部有一个静态变量 gHello,那么这个对象无论生成多少实例对于gHello在内存中只能有一份,那么如果有两个不同的实例在两个线程里面同时去读写它,就有可能出错,所以就要就要有种机制进行同步保护,STA或者MTA就是这种机制。
进程相对于一个小城镇,线程相当于这个城镇里的居民,STA(单线程套间)相当于居民房,是私有的,MTA(多线程套间)相当于旅馆,是公用的,Com对象相当于居民房或旅馆里的物品.接下去就好理解了,一个小城镇(进程)里可以有很多很多的(居民)线程,这个城镇(进程)只有一间旅馆(MTA),但可以有很多很多的居民房(STA).只有居民(线程)进入了房间(居民房或旅馆,STA或MTA)以后才能使用该房间里的物品(COM对象),居民房(STA)里的物品(COM对象)只能供这间房子的主人(创建该STA的线程)使用,其它居民(线程)不能访问.同样,只有入住到旅馆(MTA)里的居民(线程,可以有多个)才可以访问到旅馆(MTA)里的物品(com对象),但因为是公用的,所以要合理的分配(同步)才能不会产生混乱.
.NET支持两种线程模型:STA和MTA。 STA(single threaded apartments)。apartment只是一个逻辑上的概念,它可以包含一个或多个线程。一个AppDomain可以包括一个或多个apartment。STA是指该apartment中只能包含一个thread。 MTA(multi threaded apartments)。指该apartment中可以包含多个thread。 STA and MTA 之间最大的区别就是MTA 可以在同一个apartment 中使用所有的共享资源并发执行多个线程。 而多个STA虽然可以共享数据,但是不能并发执行线程,存在性能问题。 线程的创建: 当创建一个新的STA线程时,CLR会在该AppDomain中创建一个apartment和thread(从属于该apartment)。如果是创建MTA线程,则会CLR会检查该AppDomain是否存在一个存放MTA的apartment,如果存在仅创建该线程到该MTA中,否则就创建一个MTA和thread(从属于该apartment)。 我们可以设置线程的属性。例如 t.ApartmentState = ApartmentState.STA; 线程的使用区别: 我们应该仅仅在访问STA-based 的COM组件时才使用STA线程模式。可以在注册表的HKEY_CLASSES_ROOT\CLSID{Class ID of the COM component} \InProcServer32 下查看到该COM的线程模式。如果该值是Apartment,则说明该COM只能以STA模式运行。其他的值有Free(MTA),Both(STA+MTA),Single(只能在一个单一的线程中执行)。 其他情况下,我们应该使用MTA的线程,虽然需要我们费心线程间资源的同步问题。
示例: 我现在想在一个windows form的程序中实现从某个word文档复制图片并保存的方案。 具体是:打开word文档,将图片信息复制到粘贴板中,然后从粘贴板中取得图片信息,再保存到本地目录中。 说明:(本来是放在代码下面的,无奈POST之后就被代码挡住不显示了) 如果在某个按钮的事件中,直接调用该方法,那么界面将变得没有响应。所以我们需要考虑使用多线程来解决这个问题。Thread t = new Thread(new TheardStart(CopyImages); t.Start(); 如果是这样,则程序会发生错误.。要么显示出现异常,要么没异常但是Clipboard为空,取不到任何数据!为什么呢? 因为Word.Application 是Automation并且STA-Based,不能在没有指定ThreadApartment的线程中被调用。所以导致了各种错误,所以需要在t.Start();前面加上t.Apartment = ApartmentState.STA;这样就完全正常了。 对于MTA的多线程我们就见的比较多了,不再举例了。 另外一点不明白,我监视任务管理器发现,我在执行Thread t = new Thread(new TheardStart(CopyImages);t.Apartment = ApartmentState.STA; t.Start();之后该程序的进程中线程数从3个增加到6个,如果创建的是MTA的线程则只增加1。我的理解是STA线程为需要维护内部隐藏的窗口类和消息队列而增加的。 下面是实现方法: 1private void CopyImages() 2 { 3 Word.Application app = null;
Task実例
private async void button_3D_Click(object sender, EventArgs e)
{
if (String.IsNullOrEmpty(tb_fesimo.Text))
{
MessageBox.Show("Please enter the path of data.");
return;
}
if(!File.Exists(tb_fesimo.Text))
{
MessageBox.Show("Cann't open the file you selected.");
return;
}
string dataString = await ProcReadData(tb_fesimo.Text);
var myForm_3D = new Form_browser();
myForm_3D.Show();
////wait 1sec to ensure that the webBrower has done loading.
await Task.Delay(1000);
myForm_3D.m_browser.ExecuteScriptAsync(String.Format("executeFunc('{0}');", dataString));
}
private async Task<string> ProcReadData(string path)
{
int SAMPLE_NUMBER = 100;
String jsonString = "";
int length = 0;
int read_data = 0;
await Task.Run(() => {
int NO_VALIDLINE = 3; //valid data begins at line 3.
using (var reader = new StreamReader(path)) //(@"html\data.csv"))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (length >= (NO_VALIDLINE - 1))
{
var values = line.Split(',');
if (length % SAMPLE_NUMBER == 0) //1秒ごとにデータを取得。
{
read_data++;
double x = Convert.ToDouble(values[1]);
double y = Convert.ToDouble(values[2]);
double z = Convert.ToDouble(values[3]);
jsonString = jsonString + String.Format("{0},{1},{2}", x, y, z).ToString() + ";";
}
if (length % 2000 == 0)
{
lb_progress.Invoke((Action)delegate () { this.lb_progress.Text = "Reading data: " + length.ToString(); });
}
}
length++;
}
}
jsonString = jsonString.Substring(0, jsonString.Length - 1);
});
lb_progress.Invoke((Action)delegate () { this.lb_progress.Text = "Read data: " + read_data.ToString() + "/ All data: " + length.ToString(); });
Console.WriteLine("ProcReadData done.");
return jsonString;
}
}
Thread, ThreadPool, Task
Thread就是Thread,需要自己调度,适合长跑型的操作。
ThreadPool是Thread基础上的一个线程池,目的是减少频繁创建线程的开销。线程很贵,要开新的stack,要增加CPU上下文切换,所以ThreadPool适合频繁、短期执行的小操作。调度算法是自适应的,会根据程序执行的模式调整配置,通常不需要自己调度线程。 另外分为Worker和IO两个池。IO线程对应Native的overlapped io,Win下利用IO完成端口实现非阻塞IO。
Task或者说TPL (Task Parallel Library) 是一个更上层的封装,NB之处在于continuation。 continuation的意义在于:高性能的程序通常都是跑在IO边界或者UI事件的边界上的,TPL的continuation可以更方便的写这种高scalability的代码。 Task会根据一些flag,比如是不是long-running来决定底层用Thread还是ThreadPool,另外也做了些细节优化,比如同一个线程跑好几个Task,比如continuation时根据情况让CPU空转几毫秒来等待前置Task结束,都是为了减少CPU上下文切换。
结论:能用Task就用Task,底下都是用的Thread或者ThreadPool。但是要注意细节,比如告诉Task是不是long-running;比如尽量别Wait;再比如IO之后的continuation要尽快结束然后把线程还回去,有事开个Worker做,要不然会影响后面的IO,等等。另外还有个特别的是Timer,所有Timer实例都是在一个专门的Timer线程上调度的。所以不要写的很重,要不然原本已经很低的精度会更加惨不忍睹。