2

次のTickイベント ハンドラーを使用して、フォームに単一のタイマー コントロールがあります。

private void timer1_Tick(object sender, EventArgs e) {
    foreach (char c in "Andrew") {
        SendKeys.SendWait(c.ToString());
        System.Threading.Thread.Sleep(1000);
    }
}

System.Windows.Forms.Timer は UI スレッドで実行されるため 、実行中にイベント ハンドラーがさらにTickイベントをブロックし、それがAndrewAndrewAndrew...出力として提供されることを期待します。代わりに、私は得るAnAnAnAn...

最初のイベントが終了する前に、後続のTickイベントが発生して処理されるのはなぜですか?

タイマーが一度に 1 つのイベントを発生させ、ハンドラーが完了するまで完全にブロックされるようにするにはどうすればよいですか?

Thread.Sleep はコードのタイミングを計る恐ろしい方法だと思います。何が起こっているのか知りたいだけです。

4

2 に答える 2

1

あなたはメッセージ ループ経由で再突入の被害者です。timer1_Tickメッセージループを介して間接的に関数に再帰しています。何が起こっているかというと、メッセージが処理されたかどうかを監視するために、SendKeys.SendWait 別のメッセージループ内で (別のスレッドではなく) スピンアップされているということです。次に、別のスレッドで、この内部ループでメッセージが処理されている間に、タイマーが起動し、tick 関数を再度呼び出すメッセージをポストします。陽気さが続きます。

おそらく、単純化された例が役立つでしょう。これを実行して、出力を観察します。

public class Program
{
    private static Queue<Action> queue = new Queue<Action>();

    public static void Main(string[] args)
    {
        // put three things in the queue. 
        // In a simple world, they would finish in order.
        queue.Enqueue(() => DoWork(1));
        queue.Enqueue(() => DoComplicatedWork(2));
        queue.Enqueue(() => DoWork(3));

        PumpMessages();            
    }

    private static void PumpMessages()
    {
        while (queue.Count > 0)
        {
            Action action = queue.Dequeue();
            action();
        }
    }

    private static void DoWork(int i)
    {
        Console.WriteLine("Doing work: {0}", i);
    }

    private static void DoComplicatedWork(int i)
    {
        Console.WriteLine("Before doing complicated work: {0}", i);
        PumpMessages();
        Console.WriteLine("After doing complicated work: {0}", i);
    }

}`

キューに入れられた各アイテムが順番に処理される UI にはスレッド ポンプ メッセージが 1 つしかないため、一種の想定です。ただし、キューに入れられたメソッド自体がメッセージをポンピングできる場合、これは当てはまりません。この例では、3 番目の操作が実際には 2 番目の操作の前に完了しています。このDoComplicatedWork方法は、 で行われていることと似ていSendWaitます。

これを防ぐ方法についての 2 番目の質問に答える必要があります。lockそれらは再入可能であるため (つまり、同じスレッドが複数回ロックを取得できる)、Aは役に立ちません。最善の方法は、タイマーを無効にするか、メソッド内でティック ハンドラーをデタッチし、戻る前にハンドラーを再度有効化/アタッチすることです。また、単純なbooleanフラグを試して、既にメソッド内にいるかどうかを示し、そうである場合は返すこともできます。

于 2013-01-12T08:56:15.377 に答える
1

Reference Source や Reflector や ILSpy などの適切な逆コンパイラを使用して、フレームワーク コード内で何が起こっているかを調べることができます。SendKeys.SendWait() メソッドは、最終的に内部クラスでメソッドを呼び出し、SKWindow を呼び出して「待機」要求を実装します。そのメソッドは次のようになります。

public static void Flush()
{
    Application.DoEvents();
    while ((events != null) && (events.Count > 0))
    {
        Application.DoEvents();
    }
}

DoEvents() は悪名高いメソッドであり、多くのコードをクラッシュさせることで有名です。タイマーの Tick イベント ハンドラーで取得した再入可能性は、かなり無害です。このコードは、プログラムにより多くの損害を与える可能性があります。この回答で説明されている、より一般的な不快感を見つけることができます。明らかに、可能であれば SendWait() を避けたいと思うでしょう。

于 2013-01-12T11:15:08.540 に答える