none
timeSetEvent APIで発生するエラーについて RRS feed

  • 質問

  • こんにちは。
    C++/CLI で作成したクラスの話ということで、こちらで投稿させていただきます。

    timeSetEvent API を TIME_ONESHOT で時間待ちクラスを作成して、C#アプリで使用しています。
    アプリの起動後、最初はうまく働いているのですが、しばらく使い続けていると timeSetEvent が NULL を返すようになり、時間待ちが働かなくなるという現象に見まわれています。時間待ちクラスを作ってから結構経ち、これまでこのような問題が発生することはありませんでした。
    また、この現象が出たときは、アプリを再起動すると復帰します。
    これまでのところ、開発環境でデバッグ時でも出たことがありません。

    API の説明では、「関数が失敗してタイマー イベントを作成できないと NULL を返す」といった説明がありますが、具体的にはどういったことが考えられるのでしょうか? また、それを回避する方法はあるのでしょうか?

    開発環境:Win7 Ultimate SP1 (32bit)、VS2008
    実行環境:Win7 Pro SP1 (32bit)

    以上、よろしくお願いします。

    2014年1月30日 7:13

回答

  • 1.関数 TimerProc(...)と、関数Stop()が無い件を無視。
    2.未定義のメンバ変数があると想定。
    3.関数getResolution()で、メンバMMResolutionが1[ms]等適切な値に初期化されると想定。
    4.MMTimerクラスのインスタンスがアプリケーションのメイン相当部分に一つだけあると想定。

    とすると、特に問題はなさそうです。

    あとは、以下の様に、コールバック関数内で使用可能な関数に制限がありますが、

    http://msdn.microsoft.com/en-us/library/windows/desktop/dd757631(v=vs.85).aspx

    の「Remarks」は確認してますよね。

    2014年1月31日 2:04
  • ちなみにリンク先の内容、以前読んでいたのですが改めて読み返したところ、

    という記述があります。特に後者に引っかかっていませんか?

    いずれにせよ、APIからエラー理由が返されないため、そもそもこのAPIを使わない方がいいというのは先のコメントと変わりありません。

    • 回答としてマーク xmine86 2014年2月1日 2:46
    2014年1月31日 2:54

すべての返信

  • TIME_ONESHOTタイプのMMタイマーを、複数使用している場合には、
    慎重にコードする必要があるかもしれません。

    http://social.msdn.microsoft.com/Forums/vstudio/ja-JP/4c153836-a87f-4d1f-b16f-e2628753e011/timesetevent?forum=vcgeneralja

    さて、MMタイマーの「挙動」や「必要な手続き」等は、
    timeSetEvent()に渡すパラメータに依存して、かなり異なります。
    それらの情報を提示すると、もう少しましな回答が得られるかもしれません。

    • 回答の候補に設定 星 睦美 2014年1月31日 1:46
    2014年1月30日 9:00
  • 問題はさておき、timeSetEvent()のドキュメントによると成功・失敗しか返されず、GetLastError()などでどのようなエラーが発生したのか確認できないようです。その上で

    This function is obsolete. New applications should use CreateTimerQueueTimer to create a timer-queue timer.

    と書かれています。CreateTimerQueueTimer()であればどのようなエラーなのかも取得することができますし、こちらに切り替えてはどうでしょうか?

    • 回答の候補に設定 星 睦美 2014年1月31日 1:47
    2014年1月30日 12:50
  • ありがとうございます。xmine86 です。

    タイマーの使用は、メインスレッドのみで単に時間待ちを行うのが目的で行っています。
    同時に複数の使用はありません。
    また、OSの提供する機能で1ms単位の待ち時間を、なるべく精度良く安定して確保するため、MMタイマーを使うことにしました。

    以下、タイマークラスのコードから抜粋します。

    #pragma	managed(push, off)
    private struct _cbparamSet
    {
        // タイマ ID
        UINT    TimerId;
        // タイムアウト フラグ
        bool    Timeout;
    };
    #pragma	managed(pop)
    
    // マルチメディア タイマ クラス
    public ref class MMTimer
    {
    public:
        // MMTimer 生成
        MMTimer()
        {
            MMResolution = 0;
            mpcbparam = new struct _cbparamSet;
            mpcbparam->TimerId = 0;
            mpcbparam->Timeout = false;
    
            // マルチメディア タイマーの分解能を取得する
            if( !getResolution() )
                return;    // 失敗
    
            // 最小分解能を設定する
            timeBeginPeriod(MMResolution);
        }
    
        // MMTimer 消滅
        ~MMTimer()
        {
            // タイマ動作中なら停止
            Stop();
            // 最小分解能をクリア
            timeEndPeriod(MMResolution);
            // ネイティブ領域を開放
            delete mpcbparam;
        }
    
        // インターバル タイマを開始する。
        UINT    Start(UINT TimeValue)
        {
            // タイムアウト フラグをリセット
            mpcbparam->Timeout = false;
    
            // タイマ動作中は開始できない
            if( mpcbparam->TimerId != 0 )
                return 0;
            // 時間設定が無効ならエラー終了
            if( TimeValue == 0 )
                return 0;
    
            // タイマをワンショットで起動する
            mpcbparam->TimerId = 
                timeSetEvent(TimeValue, MMResolution, TimerProc, 
                        (DWORD_PTR)mpcbparam, TIME_ONESHOT);
            // タイマ識別子を返す
            return mpcbparam->TimerId;
        }
    

    と、こんな感じです。

    アプリ起動後は、一つの MMTimer オブジェクトを new して、アプリ終了までそのオブジェクトを時間待ちごとに使っています。
    待ち時間の値は、1 以上です。

    これで何かわかることがあるでしょうか?

    以上、よろしくお願いします。

    2014年1月31日 1:09
  • ありがとうございます。 xmine86 です。

    このようなタイマーがあるとは知りませんでした。

    これも試してみます。

    2014年1月31日 1:12
  • 1.関数 TimerProc(...)と、関数Stop()が無い件を無視。
    2.未定義のメンバ変数があると想定。
    3.関数getResolution()で、メンバMMResolutionが1[ms]等適切な値に初期化されると想定。
    4.MMTimerクラスのインスタンスがアプリケーションのメイン相当部分に一つだけあると想定。

    とすると、特に問題はなさそうです。

    あとは、以下の様に、コールバック関数内で使用可能な関数に制限がありますが、

    http://msdn.microsoft.com/en-us/library/windows/desktop/dd757631(v=vs.85).aspx

    の「Remarks」は確認してますよね。

    2014年1月31日 2:04
  • ちなみにリンク先の内容、以前読んでいたのですが改めて読み返したところ、

    という記述があります。特に後者に引っかかっていませんか?

    いずれにせよ、APIからエラー理由が返されないため、そもそもこのAPIを使わない方がいいというのは先のコメントと変わりありません。

    • 回答としてマーク xmine86 2014年2月1日 2:46
    2014年1月31日 2:54
  • 1.関数 TimerProc(...)と、関数Stop()が無い件を無視。
    2.未定義のメンバ変数があると想定。
    3.関数getResolution()で、メンバMMResolutionが1[ms]等適切な値に初期化されると想定。
    4.MMTimerクラスのインスタンスがアプリケーションのメイン相当部分に一つだけあると想定。

    とすると、特に問題はなさそうです。

    度々お世話になります、xmine86 です。
    長くなりすぎると思って端折ったのがいけなかったようです。

    getResolution() は、timeGetDevCaps API で最小値を取得して MMResolution にセットする関数です。
    これまで見る限りでは、MMResolution には 1 がセットされるのみ、ですね。
    その他、すべて想定されている通りです。

    また、TimeProc 内ではフラグを立てるだけで他は何もしていません。
    一応、[Remarks] についても了解しております。
    ますます分からなくなりました。

    何はともあれ、ありがとうございました。

    2014年1月31日 9:15
  • ちなみにリンク先の内容、以前読んでいたのですが改めて読み返したところ、

    という記述があります。特に後者に引っかかっていませんか?

    こちらのドキュメントは、今回の現象でいろいろ調べるうちに出会ったものですね。

    時間については、せいぜい 100ms までです。
    1プロセス辺り16個まで、というのは「同時に」ということと理解してますが、そういうことですよね。
    私としては、同時に1個まで、のつもりで使っています。

    2014年1月31日 9:30
  • いずれにせよ、APIからエラー理由が返されないため、そもそもこのAPIを使わない方がいいというのは先のコメントと変わりありません。

    xmine86 です。

    書き忘れましたが、先に教えていただいた API については、試しているところです。
    これまで MMTimer を使ってきましたが、これでよいようだったら、暫時変更していこうかと考えてます。

    2014年1月31日 9:46
  • ですか、そうなるとtimeSetEvent()が失敗する理由は、
    他の部分の影響かもしれません。

    可能であれば、タイマー部分のみを取り出したアプリを作成し、
    タイマー部分に問題が無いことを確認してみてはどうでしょう。

    また、CreateTimerQueueTimer()を検討するのは良いことだと思います。
    ただし、timeSetEvent()よりもやや精度が落ちるという報告が散見される点が、
    残念なことですね。ひどく落ちるほどではないようなので、許容できるのなら
    そっちのほうが良いかもしれません。

    2014年2月3日 1:01
  • まさかとは思いますが、単位を間違えていて100秒待ちになっていて1プロセスに16個溜まってしまったとかはありませんよね。

    それとは別の視点で、同時に1個まで100ms程度で「TimeProc 内ではフラグを立てるだけ」という用途、つまりタイムアウト検出でしょうか? これでしたら別にタイマーを用いなくても、フラグを確認する処理の代わりにGetTickCount()などで現在時刻を見れば済みそうにも思います。(開始時にGetTickCount()の値を保持しておいて、差分から経過時間を判断できます。)

    2014年2月3日 2:34
  • timeSetEvent() 単独での検証、やってみました。
    とりあえず今のところ問題は出ないようです。

    どうやらもう一度問題のアプリのコードをよく見る必要がありそうです。

    CreateTimerQueueTimer() についても試した限りでは、使えそうな感触です。
    製品に組み込むのは、もう少し先になりそうですが。

    2014年2月3日 6:38
  • 流石に、単位の間違いはありません。
    時間のパラメータはもちろん、動作の確認は、ソフト的には QueryPerformanceCounter() を、ハード的にはオシロスコープを使って確認しています。

    MMタイマーの用途としてはハードウェアを動かした後の時間待ちで、1ms~100ms の範囲で使用しており、ここにそこそこの精度を要求しています。
    タイムアウト検出のような荒くていいところでは、仰るような GetTickCount() や Environment.TickCount を使ってます。

    2014年2月3日 6:55