質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
MFC

MFC (Microsoft Fouondation Class)とは、MicrosoftがVC++用に開発したWindows用アプリケーションのフレームワークです。

Q&A

解決済

2回答

1968閲覧

VC++ MFC timeseteventを使ったマルチスレッドでのExcelfileへの書き込み

KKKM

総合スコア16

MFC

MFC (Microsoft Fouondation Class)とは、MicrosoftがVC++用に開発したWindows用アプリケーションのフレームワークです。

0グッド

0クリップ

投稿2019/08/24 09:04

編集2019/08/24 10:05

前提・実現したいこと

VC++ MFC
実現したいことExcel FILEへの同時書き込み

MFC
マルチスレッドで処理を行うために

CSampleDlg.cpp
m_uTimerID_1 = timeSetEvent(10, 1, Main_Thread, reinterpret_cast<DWORD>(hwndDlg), TIME_PERIODIC);
m_uTimerID_2 = timeSetEvent(10, 1, Main_Thread_2, reinterpret_cast<DWORD>(hwndDlg), TIME_PERIODIC);
を呼び出しております。
それぞれのスレッドで
fprintfを使いそれぞれ別のExcelFileへの書き込みを行おうと思っているのですが、うまくいきません。
Main_threadでfprintfを呼び出すと、それ以降Main_thread_2のスレッドは何も処理を行いません。

ここに質問の内容を詳しく書いてください。
(例)PHP(CakePHP)で●●なシステムを作っています。
■■な機能を実装中に以下のエラーメッセージが発生しました。

発生している問題・エラーメッセージ

エラーメッセージはなく、
ビルド、デバックは可能です。

該当のソースコード

C++
Main_thread

FILE* fp;
fp=fopen("aaa.csv","a");
fprintf(fp,"%d",W);
fclose(fp);

Main_thread_2
FILE* fp2;
fp2=fopen("bbb.csv","a");
fprintf(fp2,"%d",W);
fclose(fp2);

試したこと

Main_threadでfprintfをデバック後30秒たってからの呼び出しに変更したところ、
30秒までは、Main_thread_2はExcelへの書き込みを行いますが、
それ以降は行いません。
Main_threadでfprintfの呼び出しを永遠に行わない場合は、Main_thread_2は正しく処理を行うことは確認できています。
TimesetEventでは第二引数で第三引数の呼び出し周期を決めることができます。
処理が間に合わないことが原因と考えてこの周期を長めにするなどの変更を行いましたが解決には至りませんでした。
また、TimesetEventで新たにスレッドを設け
m_uTimerID_1 = timeSetEvent(10, 1, Main_Thread, reinterpret_cast<DWORD>(hwndDlg), TIME_PERIODIC);
m_uTimerID_2 = timeSetEvent(1000, 1, Main_Thread_2, reinterpret_cast<DWORD>(hwndDlg), TIME_PERIODIC);
m_uTimerID_3 = timeSetEvent(10, 1, Main_Thread_3, reinterpret_cast<DWORD>(hwndDlg), TIME_PERIODIC);
Main_thread_2の開始時間を遅らせてみたところ,Main_thread_2の呼び出しを境に
Main_thread_3が処理を行わなくなりました。
いろいろ調べましたがどうしても解決できませんでした。


dodox86さん
ご指摘ありがとうございました。
修正いたしましたのでご確認お願いいたします。
知識がなく大変申し訳ございません
ランタイムライブラリに関して
プロパティ>C/C++>コード生成>ランタイムライブラリ
から
マルチスレッドデバックDLL(/MDd)とありました。
ご指摘の答えとなっていなかったら申し訳ございません。

補足情報

プログラミング歴1年です。
足りない部分やアドバイスなどございましたらご指摘よろしくお願いたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dodox86

2019/08/24 09:52

ソースファイルが不完全ではありませんか? FILE* fp; fopen(fp,"aaa.csv","a"); fprintf(fp,"%d",W); fclose(fp); では fopen関数の使い方も違うし(fopen_sと混同?)、FILE *fp が正しく初期化されていないので、正しく動くはずはないのですが。あと、別のExcelファイルへ書き込むとのことですが、”aaa.csv”を指定しているので同じファイルだと思います。更に、マルチスレッド用のランタイムライブラリを正しく指定する必要もあります。
dodox86

2019/08/24 10:14

(追記を見て)ランタイムライブラリ指定にはとりあえず問題なさそうですが、FILE* fp; fopen(fp,"aaa.csv","a"); ... のコードはどうですか? これではそもそも動きません。fprintfの実行時に初期化されてない変数fpを使うので、最悪、プログラムは異常終了します。(それが原因では?)
KKKM

2019/08/24 10:21

このサイトの扱いに慣れておらず、修正した部分が見にくくなっておりました FILE* fp; fp=fopen("aaa.csv","a"); fprintf(fp,"%d",W); fclose(fp); Main_thread_2 FILE* fp2; fp2=fopen("bbb.csv","a"); fprintf(fp2,"%d",W); fclose(fp2); ソース内では上記のように書き込んでおりましたが 質問を書く際に誤って記述してしまいました。 大変申し訳ございません。
dodox86

2019/08/25 02:08 編集

hwndDlgを渡しているので、ダイアログボックスやウィンドウなどとやり取りをしているなど、実は結構色々とやっているのでないか?と思いました。だとするとy_waiwaiさんの指摘されているように、fprintfやfputsの問題ではなく、問題発現の契機でしかない可能性もあります。aaa.csvやbbb.csv を裏でExcelなどで開いていたりしませんか。また、Windows やVisual Studio のバージョンも示しましょう。結構古い、前任者のコードの保守/機能追加ではありませんか?
guest

回答2

0

ベストアンサー

質問者さんのコードを全部再現することはできなさそうなので恐らく、の意見ですが、ファイルI/Oを行うタイマー関数(Main_Thread)の読み出し周期が短すぎるのが原因のひとつだと思います

timeSetEventAPIの第1, 第2引数はミリ秒単位の指定です。
Microsoft Docs - timeSetEvent function

質問者さんのコードでは timeSetEvent(10, 1, Main_Thread...と指定していますが、結果として10ミリ秒周期で読み出す指定となっています。Windows はリアルタイムOSではないので、アプリケーションで10ミリ秒間隔で呼び出し指定してもそれは保証されず、速くても15~20ミリ秒くらいの間隔で呼ばれているかと思います。ただ、これだけ速いと同一アプリケーション内の他のスレッドがいい具合(?)に均等に呼び出されるか疑問です。

~~処理がイッパイイッパイになっているのではないでしょうか。~~タスクマネージャーで、当該アプリケーションのCPU利用率をみてみてください。また、デバッグの方法のひとつですが、OutputDebugStringと言うデバッグ用APIがあって、

C

1OutputDebugString("DEBUG!");

のようにコードを書くと、デバッグ端末、Visual Studio上でデバッグしているのであれば出力ウィンドウに文字列"DEBUG!"が出力されます。これを利用してスレッド1, スレッド2の実行状態を確認してみてください。GetTickCountと言うAPIを使えばWindowsが起動してから何ミリ秒が経過しているかが分かり、それを利用して呼び出し周期の確認もできます。

単純に、呼び出し周期を1000ミリ秒など長く指定すると状況が改善するかもしれません。もし、本当に10ミリ秒単位で処理するのが要求仕様なのであれば、別の方法を検討する必要があります。


追記しました:2019-08-24 20:57

OutputDebugStringと言うデバッグ用APIがあって、

MFCを利用している Visual Studio/C++の環境ならば TRACEATLTRACEなど、printfライクな使い方ができるマクロを利用した方が簡単かつ便利です。(詳細はググってみてください)


修正、追記しました:2019-08-25 11:00

大変失礼しました。そもそも、KKKMさんが既に試した以下のことを失念しておりました。

処理が間に合わないことが原因と考えてこの周期を長めにするなどの変更を行いましたが解決には至りませんでした。

私の回答の「処理がイッパイイッパイなのではないか?」の部分は無視してください。10ミリ秒もあればfprintfや、y_waiwaiさんとのやり取りで試されたfputsのデータ出力くらい問題無いだろうと思い直し、コードを書いて試しましたが、実際、timeSetEventで起動した複数のタイマーのスレッドは動き、問題発生は見受けられませんでした。別回答にて指摘されているように問題は別のところにありそうですね。以下は簡単に書いて試したテストコードです。タイマー関数は共用していますが、KKKMさんが使われているようなかんじで呼び出し、"aaa.csv", "bbb.csv", "ccc.csv" の3つのファイルへそれぞれfprintfでダミーデータを書き込みます。

C++

1// fopen_s, sprintf_s ... 2#define _CRT_SECURE_NO_WARNINGS 3 4#include <Windows.h> 5#include <stdio.h> 6#include <conio.h> 7 8#pragma comment(lib, "Winmm.lib") 9 10void CALLBACK TimerFuncX(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) 11{ 12 char msg[128]; 13 14 const char *pfilename = (const char*)dwUser; 15 DWORD thid = GetThreadId(GetCurrentThread()); 16 17 sprintf(msg, "START(%lu):\tThread ID=%lu, Timer ID=%d, filename=%s\n", GetTickCount(), thid, uTimerID, pfilename); 18 OutputDebugString(msg); 19 20 FILE* fp = fopen(pfilename, "a"); 21 if (fp != NULL) { 22 fprintf(fp, "%d\n", uTimerID); 23 fclose(fp); 24 } 25 26 sprintf(msg, "END(%lu):\tThread ID=%lu, Timer ID=%d\n", GetTickCount(), thid, uTimerID); 27 OutputDebugString(msg); 28} 29 30int main(int argc, char *argv[]) 31{ 32 char msg[128]; 33 sprintf_s(msg, _countof(msg), "start main.tid=%lu\n", GetThreadId(GetCurrentThread())); 34 OutputDebugString(msg); 35 36 MMRESULT timerID1 = timeSetEvent(10, 1, TimerFuncX, (DWORD_PTR)"aaa.csv", TIME_PERIODIC); 37 MMRESULT timerID2 = timeSetEvent(10, 1, TimerFuncX, (DWORD_PTR)"bbb.csv", TIME_PERIODIC); 38 MMRESULT timerID3 = timeSetEvent(10, 1, TimerFuncX, (DWORD_PTR)"ccc.csv", TIME_PERIODIC); 39 40 printf("enter any key to quit.\n"); 41 _getch(); 42 43 timeKillEvent(timerID1); 44 timeKillEvent(timerID2); 45 timeKillEvent(timerID3); 46 47 printf("done.\n"); 48 49 return 0; 50}

Visual Studio 2017 でビルドし、Windows 7下で稼動させましたが、当然のように特に問題ないです。実行時のOutpuDebugStringによるデバッグ出力結果は以下です。

start main.tid=992 START(6265062): Thread ID=8072, Timer ID=16, filename=aaa.csv END(6265062): Thread ID=8072, Timer ID=16 START(6265062): Thread ID=8072, Timer ID=33, filename=bbb.csv END(6265062): Thread ID=8072, Timer ID=33 START(6265062): Thread ID=8072, Timer ID=50, filename=ccc.csv END(6265062): Thread ID=8072, Timer ID=50 START(6265078): Thread ID=8072, Timer ID=33, filename=bbb.csv END(6265078): Thread ID=8072, Timer ID=33 START(6265078): Thread ID=8072, Timer ID=16, filename=aaa.csv END(6265078): Thread ID=8072, Timer ID=16 START(6265078): Thread ID=8072, Timer ID=50, filename=ccc.csv END(6265078): Thread ID=8072, Timer ID=50 START(6265078): Thread ID=8072, Timer ID=33, filename=bbb.csv END(6265078): Thread ID=8072, Timer ID=33 ...ずっと続く

コードを読んでいただくと分かるように、タイマー関数の呼び出し時にスレッドのIDを出力しています。注意してほしいのはタイマー関数TimeFuncXsetTimeEventで3回登録し、それぞれが動くことを期待していますが、これら3つの呼び出し時のスレッドIDは同じであり、つまりはスレッド自体は1つであることを示しています。完全にマルチスレッドで並列に呼ばれる訳ではなさそうです。呼ばれる場合もあるかもしれませんが、Windows任せです。ご参考まで。

投稿2019/08/24 11:34

編集2019/08/25 02:11
dodox86

総合スコア9183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KKKM

2019/08/25 08:04

ご回答いただきありがとうございます。 また、丁寧にサンプルまで作ってくださりありがとうございます。 エクセルファイルの同時書き込みが出来たことを確認くださりありがとうございます。 原因はtimseseteventにあるのではなく、他にある可能性が高そうですね。 >>Windows やVisual Studio のバージョンも示しましょう。結構古い、前任者のコードの保守/機能追加ではありませんか? windows10 visualstudio2010 前任者のコードの機能追加に当たります。 Main_threadでの主な処理はソケット通信になっています。 Timeseteventの話と少しずれるので質問欄には記載しませんでしたが、 デバックを行うとこのソケット通信自体のところでエラーが起きデバックが中止されます。 ただし、何度もデバックを繰り返すと成功し、それ以降のデータの送受信はうまくいきません。
dodox86

2019/08/25 08:15

> Main_threadでの主な処理はソケット通信になっています。 timeSetEventを使うかぎり、テストプロで示したようにスレッド自体は同じ可能性が高いので、Main_threadで長時間処理を占有すると別の関数Main_thread2が呼ばれてないかもしれません。関数頭でデバッグ出力するなどして、流れを確認してみてください。デバッガーで逐一、停止(ブレイク)させたりすると挙動や流れが変わって問題が再現しないこともあります。ソケット通信のところで相手からの受信待ちで停まっている、とかじゃないでしょうかね。(あくまで想像です)
KKKM

2019/08/26 06:27

様々なアドバイス頂きありがとうございます。 大変勉強になりました。 現在は原因がどこにあるのかわからないので別の方法でExcelファイルに所望のデータを出力できるように書き換えております。 ただ最後に、もしよろしければなにか意見頂きたいのですがよろしいでしょうか。 Main_thread内で sprintf関数を呼び出しています。 sprintf(cBuf,"%d",Y); このsprintfが悪さをする可能性はありますでしょうか。 その他に、 memset,stract関数も使っています
dodox86

2019/08/26 07:13 編集

> このsprintfが悪さをする可能性はありますでしょうか。 > memset,stract関数も使っています ありえます。ただ、マルチスレッド用のランタイムライブラリを正しくリンクしているのであれば関数自体ではなく、使っているプログラマーが悪いコードを書いた場合です。これはtimeSetEventへの影響に限りませんが、例えば char cBuf[3]; sprintf(cBuf, "%d", 123); だと cBufの領域3バイトを越えてメモリを壊しますし、同様に memset(cBuf, 0, 4); でも壊しますし、 strcpy(cBuf, "12"); strcat(cBuf, "3"); のような使い方でも壊します。いずれも、cBufのサイズを越えた操作なので、バッファオーバーフロー、バッファオーバーランなど呼ばれます。 上記のようなコードは実行した瞬間に問題が起きるとは限らなくて、プログラムを稼動させているとジワジワ問題が起きて、あとで一見、関係無いところで突然異常終了したりします。strcpyやstrcat はサイズチェックは行わないので、注意深く使わないと未知の入力データが入ったときに簡単に壊れます。 回答では直接関係なさそうなので指摘しませんでしたが sprintfやstrcatに関しては、Visual Studio 2010 ならもう少し安全、と言うか上記のようなときに即座にエラーを検出する sprintf_s, strcat_s と言うセキュアな代替関数があり、それらを使うのが推奨されています。コンパイル時に以下のような警告が出力されているのではないでしょうか。(前任者により、無視するプロジェクト設定になっている可能性もあります) 警告 1 warning C4996: 'strcat': This function or variable may be unsafe. Consider using strcat_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. d:\project\x\x.cpp 17 1 x
dodox86

2019/08/26 07:17

補足ですが、 > 現在は原因がどこにあるのかわからないので別の方法でExcelファイルに所望のデータを出力できるように書き換えております。 今、問題が発現していることが幸いな場合もあります。やり方を変えると問題が潜在化(根本的に直っていないので、いずれ別のかたちで発現する)してしまうこともあるので、注意してみてください。
KKKM

2019/08/26 07:52

ご回答ありがとうございます。 >>今、問題が発現していることが幸いな場合もあります。やり方を変えると問題が潜在化(根本的に直っていないので、いずれ別のかたちで発現する)してしまうこともあるので、注意してみてください。 それがとても怖いですね、、、 解決したいですが、、 sprintfなどの危険性に関して、詳しくわかりやすく説明してくださりありがとうございます。自身のソースを見たところサイズは大きくとられていたので、おそらく問題はなさそうです。 しかし、よい勉強となりました。ありがとうございます。
dodox86

2019/08/26 11:45

最初、外した上に最終的にも問題解決への直接的な回答ではなかったところへBAをいただいて逆に恐縮してます。(質問者さんの自己解決でも良かったかも?) ともあれ、ありがとうございます。
guest

0

fprintf関数がリエントラントになっていないから、ですね。
外部変数などを使用している関数は、マルチスレッドで同時実行できません。

そこんところを、同時実行できる関数に書き換える必要があります

投稿2019/08/24 09:43

y_waiwai

総合スコア87719

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dodox86

2019/08/24 10:09

少なくともVisual Studio環境に限って言えば、標準関数fprintfでもマルチスレッド対応/スレッドセーフのランタイムライブラリが既定かと思います。
KKKM

2019/08/24 10:17

ご回答いただきありがとうございます。 Main_thread fprintf(fp,"\n"); Main_thread_2 fprintf(fp2,"\n"); と変更しましたが、変化はありませんでした。 >>外部変数などを使用している関数は、マルチスレッドで同時実行できません。 引数にとっているfp,fp2はともにローカル変数で fpの宣言、定義書き込み等は以下で行い void CALLBACK CSampleDlg::Main_Thread(UINT uiID, UINT uiNo, DWORD dwCookie, DWORD dwNo1, DWORD dwNo2) {......} fp2は void CALLBACK CSampleDlg::Main_Thread_2(UINT uiID, UINT uiNo, DWORD dwCookie, DWORD dwNo1, DWORD dwNo2) { CSampleDlg* pSample = (CSampleDlg*)dwCookie; pSample->k.AAA();  } int k::AAA(){ fp2の定義宣言書き込み } としています。 何か問題はございますでしょうか。
y_waiwai

2019/08/24 10:21

fprintf関数自体のはなしです。 各タイマスレッド関数をなにもしない関数にすればどうなります?
KKKM

2019/08/24 10:37

プログラムの規模が大きく、Main_thread内をなにも行わない関数とすると、いたるところで問題が起きてしまい、難しいです。なので問題がありそうなFILE関係のみコメントアウトします。 Mainthread_2に関しては可能ですので試してみました、(Main_thread_2を完全に何もしない関数とするとMain_thread_2の処理を行おうとしてるのかも分からないのでTRACE関数だけで使いました。) 結果 二つのスレッドは処理を行います。 上記の状態で Main_thread_2をもとの状態に戻し、 ファイル関係の関数の呼び出しも行う場合。 この場合も各スレッドは正常に動作します。(Main_threadではFILE関係の関数を使っていません)
y_waiwai

2019/08/24 10:44

そうやって、原因となる処理を絞り込んでいきましょう fprintfが怪しいとなったら、fputs関数にしてみるとかしてみてください なにをすればだめになるか、を特定します
KKKM

2019/08/24 10:59

ありがとうございます。 Main_thread_2 ファイル関係の関数の呼び出しなし。 Main_thread ファイル関係の関数の呼び出し有。 の場合、Main_threadのみ行われる。 Main_thread_2 ファイル関係の関数の呼び出しあり。 Main_thread ファイル関係の関数(fputs)の呼び出し有。 の場合 Main_thread内のみ実行 ほかのファイル関係の関数も試そうと思いますが、 Main_threadないでfprintf,fpus関数を使うと、Main_thread2が処理を行いません。 ただ、fopen,fcloseだけだと処理は正常に行われます。 この場合も各スレッドは正常に動作します。(Main_threadではFILE関係の関数を使っていません)
KKKM

2019/08/24 11:01

すみません 最後の行の >>この場合も各スレッドは正常に動作します。(Main_threadではFILE関係の関数を使っていません は無視してください
y_waiwai

2019/08/24 11:21

fputsだけでもダメですか。そうなるとファイル操作関係以外の処理でおかしくなってるという可能性もありますね 各スレッドのその他の処理も視野に入れて探ってみてください
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問