MFCのマルチスレッドでCStringArrayを操作するには?

解決


TnR  2008-07-07 08:48:40  No: 68665

お世話になっております。VC++ 6 MFC (OSはXP)を使っています。

CInternetSession/CHttpConnection/CHttpFileを使って
サーバと通信するアプリを作っています。
そこで、通信待ちの際にプログレスバーを表示したいと考えてます。

AfxBeginThreadを使ってワーカスレッドを作成し、通信を行い、
メインスレッドでプログレスバー用ダイアログを呼ぶことにしました。

しかし、作成したスレッド側でアサートに引っ掛かってしまいます。

CString::CString(const CString& stringSrc)
{
    ASSERT(stringSrc.GetData()->nRefs != 0);  // ←エラー

呼び出し側は、
    CString strData = This->data.GetAt(0);
となっています。

どうすれば、このエラーを回避出来るのでしょうか?
重要だと思う箇所を抜粋したソースを載せます。

class CHttpHoge {
    CStringArray data;                  // 送信データ
    CCriticalSection criticalSection;   // クリティカルセッション
    static UINT Connect(LPVOID);        // マルチスレッド用
    CStringArray response;              // 受信データ
    // (略)
public :
    void GetRequest(CStringArray&);     // 送受信関数
    BOOL Lock() { return criticalSection.Lock(); }
    BOOL Unlock() { return criticalSection.Unlock(); }
    // (略)
};

void CHttpHoge::GetRequest(CStringArray& response) {

    AfxBeginThread(Connect, this);
    response.Copy(this->response);

    return;
}

/* static */ UINT CHttpHoge::Connect(LPVOID pParam) {

    CHttpHoge *This = static_cast<CHttpHoge *>(pParam);
    This->Lock();

    // 処理( This->data を操作すると落ちる )

    This->Unlock();
    return 0;
}

かなり行き詰ってしまっております。

直接の解決方法でなくても構いませんので、
宜しくお願いします。


そだ  2008-07-07 16:26:40  No: 68666

CStringのコンストラクタで落ちているのはStringArrayが文字列を
1つも持っていないのに無理やり取得しているからでは?
要素数みてからアクセスしたらどうでしょ。

CString strData;
if(This->data.GetCount() > 0 ) {
   strData = This->data.GetAt(0);
} else {
   //エラー処理など
}

それとスレッドを開始するGetRequestでいきなりresponseのコピーを渡しても
CHttpHoge::responseに具体的な結果が入るのはスレッドが回りきってから
なので意味がないかと。

This->dataの操作で落ちるのは操作の仕方が悪いかメインスレッド側で
ロックがかかってないのでは?


TnR  2008-07-08 07:54:14  No: 68667

そださん、返信ありがとうございます。

私がスレッドというものを理解出来ていなかったようです。

> GetRequestでいきなりresponseのコピーを渡しても

AfxBeginThread(Connect, this);
で自分のスレッドがずっと待機してくれているものだと勘違いしておりました。
なので、次の行へ移動することなど考えておりませんでした。

ちょっと話がタイトルと違ってきてしまうのですが、
どうすれば、

AfxBeginThread(Connect, this);
// ※ここ※
response.Copy(this->response);

で通信スレッドを待てるのでしょうか?

フラグを立てて、

while (bLoop) { // 通信スレッドが、処理が終わった後にFALSEにする
  Sleep(10);
}

などとしてみたのですが、メインスレッドの方で実行されている
プログレスバーの描画が行われません。
(メッセージループがまわっていないのだと思います)

言い忘れておりましたが、ダイアログベースです。
宜しくお願いします。


Ban  2008-07-08 10:23:26  No: 68668

Sleepのループは屋ってはいけない典型です。

一つは、比較的正攻法で、そこで待たずに処理を分割し、
状態を変えるなどして一度処理を抜けます。
プログレスは、ワーカ内からメッセージを投げるなどして進めます。
処理完了すれば状態を変更します。

もう一つは、GetMessage, DispatchMessage等を呼び出して、
むりやりメッセージループを回す手もありますが、
あまり綺麗ではないと思います。


TnR  2008-07-08 16:12:03  No: 68669

Banさん、返信ありがとうございます。

> 状態を変えるなどして一度処理を抜けます。

なるほど。通常のアイドリング(?)状態に戻すわけですね。

> プログレスは、ワーカ内からメッセージを投げるなどして進めます。

プログレスは値が必要ないプログレス(ファイル検索などで表示される
メータの溜まらないプログレスバー)を自作しましたので、使いたいと思っております。

ですので、WM_PAINTとWM_TIMERが受け取れればOKです。
データの送受信は必要ありませんので、
もっと簡単にマルチスレッドを作れないのかなと考えておりました。

> 処理完了すれば状態を変更します。

この状態を変更するというのは、どういうことでしょうか?
宜しければ詳しく教えてください。

ちょっと思いついたのですが、処理が終わったときに、
(WM_APP + 1) などをメインスレッドの親ダイアログに投げれば
出来そうな気がします。

まずは「一度処理を抜ける」というのをやってみようと思います。
宜しくお願いします。


そだ  2008-07-09 20:11:20  No: 68670

状態の変更は通信中・非通信中のアプリケーションのふるまい
(プログレスを表示・非表示するとか、通信ボタンを押せないようにして2重通信を防ぐとか・・・)
をそのタイミングで変更しろってことだと思いますが・・・違ってたらごめんなさい。


TnR  2008-07-11 16:41:54  No: 68671

そださん、返信ありがとうございます。

> アプリケーションのふるまい

なるほど。そういうことでしたか。
確かに、通信中に再度通信ボタンは押せないように対処しようと
思います。

Banさんの、
> そこで待たずに処理を分割し、

で送受信情報クラスを new してスレッドへ渡し、
スレッドで情報をセットし、
通信終了後にメインスレッドへそのポインタをSendMessageで返却する
という方法で作ってみようと思います。

そださん、Banさん、どうもありがとうございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加