49

セカンダリ スレッドで操作の連続ループを実行する Visual C# プログラムを作成しています。そのスレッドがタスクを終了したときに、イベントハンドラーをトリガーしたい場合があります。私のプログラムはそれを行いますが、イベントハンドラーがトリガーされると、セカンダリスレッドはイベントハンドラーが終了するまで待機してからスレッドを続行します。継続させるにはどうすればいいですか?これが私が現在それを構成している方法です...

class TestClass 
{
  private Thread SecondaryThread;
  public event EventHandler OperationFinished;

  public void StartMethod()
  {
    ...
    SecondaryThread.Start();      //start the secondary thread
  }

  private void SecondaryThreadMethod()
  {
    ...
    OperationFinished(null, new EventArgs());
    ...  //This is where the program waits for whatever operations take
         //place when OperationFinished is triggered.
  }

}

このコードは、私のデバイスの 1 つの API の一部です。OperationFinished イベントがトリガーされたときに、クライアント アプリケーションが API 操作を実行することなく、必要なことを実行できるようにしたい (つまり、それに応じて GUI を更新する)。

また、イベント ハンドラーにパラメーターを渡したくない場合は、OperationFinished(null, new EventArgs())?を使用して正しい構文を使用できます。

4

7 に答える 7

65

では、リスナーがバックグラウンド スレッドをブロックしないようにイベントを発生させたいと思いませんか? 例を作るために数分待ってください。それはとても簡単です:-)

それでは始めましょう: 最初に重要な注意事項があります。呼び出すたびにBeginInvoke、対応する を呼び出す必要がありますEndInvoke。そうしないと、呼び出されたメソッドが例外をスローしたり、値を返したりした場合、ThreadPool スレッドがプールに解放されず、スレッド リークが発生します。

class TestHarness
{

    static void Main(string[] args)
    {
        var raiser = new SomeClass();

        // Emulate some event listeners
        raiser.SomeEvent += (sender, e) => { Console.WriteLine("   Received event"); };
        raiser.SomeEvent += (sender, e) =>
        {
            // Bad listener!
            Console.WriteLine("   Blocking event");
            System.Threading.Thread.Sleep(5000);
            Console.WriteLine("   Finished blocking event");
        };

        // Listener who throws an exception
        raiser.SomeEvent += (sender, e) =>
        {
            Console.WriteLine("   Received event, time to die!");
            throw new Exception();
        };

        // Raise the event, see the effects
        raiser.DoSomething();

        Console.ReadLine();
    }
}

class SomeClass
{
    public event EventHandler SomeEvent;

    public void DoSomething()
    {
        OnSomeEvent();
    }

    private void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            var eventListeners = SomeEvent.GetInvocationList();

            Console.WriteLine("Raising Event");
            for (int index = 0; index < eventListeners.Count(); index++)
            {
                var methodToInvoke = (EventHandler)eventListeners[index];
                methodToInvoke.BeginInvoke(this, EventArgs.Empty, EndAsyncEvent, null);
            }
            Console.WriteLine("Done Raising Event");
        }
    }

    private void EndAsyncEvent(IAsyncResult iar)
    {
        var ar = (System.Runtime.Remoting.Messaging.AsyncResult)iar;
        var invokedMethod = (EventHandler)ar.AsyncDelegate;

        try
        {
            invokedMethod.EndInvoke(iar);
        }
        catch
        {
            // Handle any exceptions that were thrown by the invoked method
            Console.WriteLine("An event listener went kaboom!");
        }
    }
}
于 2009-12-16T17:26:12.623 に答える
17

With the Task Parallel Library it is now possible to do the following:

Task.Factory.FromAsync( ( asyncCallback, @object ) => this.OperationFinished.BeginInvoke( this, EventArgs.Empty, asyncCallback, @object ), this.OperationFinished.EndInvoke, null );
于 2013-05-02T11:00:29.267 に答える
12

また、イベント ハンドラーにパラメーターを渡したくない場合、OperationFinished(null, new EventArgs()) を使用して正しい構文を使用できますか?

いいえ。通常、次のように呼び出します。

OperationFinished(this, EventArgs.Empty);

常にオブジェクトを送信者として渡す必要があります-パターンで期待されています(通常は無視されますが)。EventArgs.Empty は new EventArgs() よりも優れています。

これを別のスレッドで起動するには、おそらくスレッド プールを使用するのが最も簡単な方法です。

private void RaiseOperationFinished()
{
       ThreadPool.QueueUserWorkItem( new WaitCallback( (s) =>
           {
              if (this.OperationFinished != null)
                   this.OperationFinished(this, EventArgs.Empty);
           }));
}

そうは言っても、別のスレッドでイベントを発生させることは、予期しない動作を引き起こす可能性があるため、完全に文書化する必要があります。

于 2009-12-16T17:13:09.790 に答える
6

イベント デリゲートで BeginInvoke メソッドと EndInvoke メソッドを試してください。これらはすぐに戻り、ポーリング、待機ハンドル、またはコールバック関数を使用して、メソッドが完了したときに通知することができます。概要については、こちらを参照してください。あなたの例では、イベントは使用するデリゲートです

于 2009-12-16T17:17:52.513 に答える
4

以下の方法2または方法3が役立つかもしれません:)

public partial class Form1 : Form
{
    private Thread SecondaryThread;

    public Form1()
    {
        InitializeComponent();

        OperationFinished += callback1;
        OperationFinished += callback2;
        OperationFinished += callback3;
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        SecondaryThread = new Thread(new ThreadStart(SecondaryThreadMethod));
        SecondaryThread.Start();
    }

     private void SecondaryThreadMethod()
     {
        Stopwatch sw = new Stopwatch();
        sw.Restart();

        OnOperationFinished(new MessageEventArg("test1"));
        OnOperationFinished(new MessageEventArg("test2"));
        OnOperationFinished(new MessageEventArg("test3"));
        //This is where the program waits for whatever operations take
             //place when OperationFinished is triggered.

        sw.Stop();

        Invoke((MethodInvoker)delegate
        {
            richTextBox1.Text += "Time taken (ms): " + sw.ElapsedMilliseconds + "\n";
        });
     }

    void callback1(object sender, MessageEventArg e)
    {
        Thread.Sleep(2000);
        Invoke((MethodInvoker)delegate
        {
            richTextBox1.Text += e.Message + "\n";
        });
    }
    void callback2(object sender, MessageEventArg e)
    {
        Thread.Sleep(2000);
        Invoke((MethodInvoker)delegate
        {
            richTextBox1.Text += e.Message + "\n";
        });
    }

    void callback3(object sender, MessageEventArg e)
    {
        Thread.Sleep(2000);
        Invoke((MethodInvoker)delegate
        {
            richTextBox1.Text += e.Message + "\n";
        });
    }

    public event EventHandler<MessageEventArg> OperationFinished;

    protected void OnOperationFinished(MessageEventArg e)
    {
        //##### Method1 - Event raised on the same thread ##### 
        //EventHandler<MessageEventArg> handler = OperationFinished;

        //if (handler != null)
        //{
        //    handler(this, e);
        //}

        //##### Method2 - Event raised on (the same) separate thread for all listener #####
        //EventHandler<MessageEventArg> handler = OperationFinished;

        //if (handler != null)
        //{
        //    Task.Factory.StartNew(() => handler(this, e));
        //}

        //##### Method3 - Event raised on different threads for each listener #####
        if (OperationFinished != null)
        {
            foreach (EventHandler<MessageEventArg> handler in OperationFinished.GetInvocationList())
            {
                Task.Factory.FromAsync((asyncCallback, @object) => handler.BeginInvoke(this, e, asyncCallback, @object), handler.EndInvoke, null);
            }
        }
    }
}

public class MessageEventArg : EventArgs
{
    public string Message { get; set; }

    public MessageEventArg(string message)
    {
        this.Message = message;
    }
}

}

于 2015-02-24T04:36:51.100 に答える
0

UI を更新するデリゲートとして子スレッドに渡すメソッドを定義することを好みます。最初にデリゲートを定義します。

public delegate void ChildCallBackDelegate();

子スレッドでデリゲート メンバーを定義します。

public ChildCallbackDelegate ChildCallback {get; set;}

呼び出しクラスで、UI を更新するメソッドを定義します。別のスレッドから呼び出されるため、ターゲット コントロールのディスパッチャーでラップする必要があります。BeginInvoke に注意してください。このコンテキストでは、EndInvoke は必要ありません。

private void ChildThreadUpdater()
{
  yourControl.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background
    , new System.Threading.ThreadStart(delegate
      {
        // update your control here
      }
    ));
}

子スレッドを起動する前に、その ChildCallBack プロパティを設定します。

theChild.ChildCallBack = new ChildCallbackDelegate(ChildThreadUpdater);

次に、子スレッドが親を更新したい場合:

ChildCallBack();
于 2009-12-16T23:29:51.783 に答える
0

BackgroundWorkerクラスを見てください。私はそれがまさにあなたが求めていることをしていると思います。

編集:あなたが求めていると思うのは、バックグラウンド タスク全体のごく一部しか完了していないときにイベントを発生させる方法です。BackgroundWorker は、プロセス全体の一部が完了したことをメイン スレッドに報告できる「ProgressChanged」というイベントを提供します。次に、すべての非同期作業が完了すると、「RunWorkerCompleted」イベントが発生します。

于 2009-12-16T17:08:01.350 に答える