問題の説明
数日前に、シリアルポートSERIALPORTクラスを使用してテストプログラムを書いたとき、シリアル・ポート・インターフェース・カード死んで終了します。
参考ブログ死んインターフェイスカードをプログラミングする理由の窓、死の原因はインターフェイスカード来る:によるリソース競合にメインスレッドと他のスレッドやロックを、デッドロックがありました。
参考記事はほとんど知っているあなたのコード内でそのステップカードのどちらをどのように決定するか、Winフォームインタフェース仮死のか?試運転一時停止をクリックすると、UIスレッド機能スタックは、直接コードの行数をブロックて位置表示、問題が閉じるSERIALPORTクラス()メソッドで発生する特定。
参考記事C#のシリアル動作シリーズ(2) -はじめ章、なぜそれがデッドロックでプログラムをシャットダウンします私のシリアルポート?類似ソリューションとソリューションのほとんどのオンライン記事:BOOLと閉会、クローズシリアルポートをリスニングマーカーの2種類を定義し、前にそれを判断するために、データを受け取ります。私は個人的にこのアプローチを受け入れていない、私はより良い方法があると感じる、と記事はについてです、それは明らかではありません。
理由をご確認ください
好奇心旺盛の原理に基づいて、私は、問題の原因を発見し続けています。
死のコード・インターフェース・カードの原因を見てみましょう:
void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//获取串口读取的字节数
int n = comm.BytesToRead;
//读取缓冲数据
comm.Read(buf, 0, n);
//因为要访问ui资源,所以需要使用invoke方式同步ui。
this.Invoke(new Action(() =>{...界面更新,略}));
}
private void buttonOpenClose_Click(object sender, EventArgs e)
{
//根据当前串口对象,来判断操作
if (comm.IsOpen)
{
//打开时点击,则关闭串口
comm.Close();//界面卡死的原因
}
else
{...}
}
問題は、上記のコードで発生し、原則は、私が唯一の問題を見つけるために、.NETのソースコードを参照することができ、まだ明らかではありません。
SERIALPORTクラスオープン()メソッド
次のようにSERIALPORTクラスソースclose()メソッドは次のとおりです。
public void Open()
{
//省略部分代码...
internalSerialStream = new SerialStream(portName, baudRate, parity, dataBits, stopBits, readTimeout,
writeTimeout, handshake, dtrEnable, rtsEnable, discardNull, parityReplace);
internalSerialStream.SetBufferSizes(readBufferSize, writeBufferSize);
internalSerialStream.ErrorReceived += new SerialErrorReceivedEventHandler(CatchErrorEvents);
internalSerialStream.PinChanged += new SerialPinChangedEventHandler(CatchPinChangedEvents);
internalSerialStream.DataReceived += new SerialDataReceivedEventHandler(CatchReceivedEvents);
}
各実行SERIALPORTクラスオープン()型SerialStreamの方法のオブジェクトのインスタンスが表示され、イベントハンドラがCatchReceivedEvents DataReceivedイベントSerialStreamインスタンスにバインドされています。
SerialStreamソースカテゴリCatchReceivedEvents方法は次のとおりです。
private void CatchReceivedEvents(object src, SerialDataReceivedEventArgs e)
{
SerialDataReceivedEventHandler eventHandler = DataReceived;
SerialStream stream = internalSerialStream;
if ((eventHandler != null) && (stream != null)){
lock (stream) {
bool raiseEvent = false;
try {
raiseEvent = stream.IsOpen && (SerialData.Eof == e.EventType || BytesToRead >= receivedBytesThreshold);
}
catch {
// Ignore and continue. SerialPort might have been closed already!
}
finally {
if (raiseEvent)
eventHandler(this, e); // here, do your reading, etc.
}
}
}
}
このイベントは、私たちのイベントハンドラは、シリアルデータを受信するために使用されるDataReceived、DataReceivedイベント自体をトリガーのSerialStreamクラスCatchReceivedEvents方法を見ることができます。
DataReceivedイベントハンドラは、ErrorReceived、PinChangedも同様のロック(ストリーム){...}のブロックが実行されています。
SERIALPORTクラスのclose()メソッド
次のようにSERIALPORTクラスソースclose()メソッドは次のとおりです。
// Calls internal Serial Stream's Close() method on the internal Serial Stream.
public void Close()
{
Dispose();
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected override void Dispose( bool disposing )
{
if( disposing ) {
if (IsOpen) {
internalSerialStream.Flush();
internalSerialStream.Close();
internalSerialStream = null;
}
}
base.Dispose( disposing );
}
あなたは閉じる()メソッドの実装は、最終的に廃棄(ブール値が破棄)メソッドを呼び出して、見ることができます。
親クラスへのMicrosoft SERIALPORTクラスは、廃棄(ブール処分)方法はbase.Dispose(処分)を実行internalSerialStream.Close()メソッド、実行する前に書き換えられているSERIALPORTインスタンスの実行を閉じる()メソッドをオフにしますが例としては、SERIALPORT例の内部に親クラスをSerialStream [閉じる()操作を行います。
base.Dispose(廃棄)メソッドは、焦点を当てていません我々はinternalSerialStream.Close()メソッドを見て。
SerialStreamソースカテゴリは、close()メソッドを見つけ閉じる方法を説明されていない親クラスのclose()メソッドは、以下のソースコードを直接見て、親クラスをオーバーライドしません。
public virtual void Close()
{
Dispose(true);
GC.SuppressFinalize(this);
}
閉じるSerialStreamの親クラスのメソッドは、廃棄(true)を呼び出しますが、SerialStreamクラスは親クラスの廃棄(ブール処分)メソッドをオーバーライドし、次のように、ソースコードは次のとおりです。
protected override void Dispose(bool disposing)
{
if (_handle != null && !_handle.IsInvalid) {
try {
//省略一部分代码
}
finally {
// If we are disposing synchronize closing with raising SerialPort events
if (disposing) {
lock (this) {
_handle.Close();
_handle = null;
}
}
else {
_handle.Close();
_handle = null;
}
base.Dispose(disposing);
}
}
}
閉じるSerialStreamの親クラスのメソッドは、廃棄(true)を呼び出して、上記のコードは、ロック(この)の文、実行されますSerialStreamインスタンスの実行を閉じる()メソッド自体をロックしますが。
デッドロックの理由
要約すると、ソースコードの私たちの以前の分析の結果:
- DataReceivedイベントハンドラは、ロック(ストリーム){...}ブロックで実行されます
- 実行SERIALPORT閉じる()閉じるSerialStream例としては、例えば、最初のインスタンスが場合メソッドSERIALPORTの内部意志
- 場合、実施例の方法で実行SerialStreamクローズ()の例には、それ自体をロックします
補助スレッドがDataReceivedイベントハンドラハンドルのシリアルデータを呼び出しますが、インターフェースを更新していない、インターフェイスをクリックし、「閉じる」ボタンをSERIALPORTインスタンスを閉じる()メソッドを呼び出して、UIスレッドがセカンダリスレッドで(ストリーム)をロックするときに解放ストリームスレッドを待っていますロック。
データを処理するワーカースレッドが終了すると問題へのインタフェースを更新する準備ができたとき、UIスレッドのDataReceivedイベントハンドラthis.Invoke()待機実行するために委託されてきたが、今回は、UIスレッドはまた、SERIALPORT close()メソッドのインスタンスで停止しますDataReceivedイベントハンドラの実行が完了するのを待ちます。
この場合、スレッドのデッドロック、両側には行きません。
デッドロックを解決します
ほとんどのオンラインの方法は、ブール値と閉会リスニングマーカーの2種類、クローズシリアルポートを定義し、前にそれを判断するために、データを受信しています。
私のアプローチがあるデリゲートリターンを実行するUIスレッドを待たずに、this.BeginInvoke()更新インタフェースを持つDataReceivedイベントハンドラ、ストリームスレッドロックがまもなくリリースされる、SERIALPORTインスタンスのclose()メソッドは、待つ必要はありません。
概要
質問への最終的な答えは非常に簡単ですが、私は多くを得る、.NETソースコードレビュープロセスにおける問題の原因を見つけます。これは、.NETソースコードの、このような深さを見るのは初めてです、我々は問題を解決するには、まだ非常に有用であることがわかりました。結果は、問題の解決が最も重要であり、重要ではありません。