Как дождаться вызова события C#?

у меня есть Sender класс, который посылает Message на IChannel:

public class MessageEventArgs : EventArgs {
  public Message Message { get; private set; }
  public MessageEventArgs(Message m) { Message = m; }
}

public interface IChannel {
  public event EventHandler<MessageEventArgs> MessageReceived;
  void Send(Message m);
}

public class Sender {
  public const int MaxWaitInMs = 5000;
  private IChannel _c = ...;

  public Message Send(Message m) {
    _c.Send(m);
    // wait for MaxWaitInMs to get an event from _c.MessageReceived
    // return the message or null if no message was received in response
  }
}

когда мы отправляем сообщения,IChannel иногда дает ответ в зависимости от того, какой Message был направлен на повышение MessageReceived событие. Аргументы события содержат сообщение, представляющее интерес.

я хочу Sender.Send() метод, чтобы ждать в течение короткого времени, чтобы увидеть, если это событие возникает. Если так, я верну его MessageEventArgs.Message собственность. Если нет, я возвращаю null Message.

как я могу ждать здесь? Я бы предпочел не делать резьбовую работу с ManualResetEvents и такие, так придерживаясь регулярных events было бы оптимальным для меня.

5 ответов


использовать AutoResetEvent.

вот это:

public class Sender
{
    public static readonly TimeSpan MaxWait = TimeSpan.FromMilliseconds(5000);

    private IChannel _c;
    private AutoResetEvent _messageReceived;

    public Sender()
    {
        // initialize _c
        this._messageReceived = new AutoResetEvent(false);
        this._c.MessageReceived += this.MessageReceived;
    }

    public Message Send(Message m)
    {
        this._c.Send(m);
        // wait for MaxWaitInMs to get an event from _c.MessageReceived
        // return the message or null if no message was received in response


        // This will wait for up to 5000 ms, then throw an exception.
        this._messageReceived.WaitOne(MaxWait);

        return null;
    }

    public void MessageReceived(object sender, MessageEventArgs e)
    {
        //Do whatever you need to do with the message

        this._messageReceived.Set();
    }
}

вы пытались назначить функцию для асинхронного вызова делегату, а затем вызвать mydelegateinstance.Метод BeginInvoke?

Linky для справки.

В приведенном ниже примере, просто позвоните

FillDataSet(ref table, ref dataset);

и он будет работать как по волшебству. :)

#region DataSet manipulation
///<summary>Fills a the distance table of a dataset</summary>
private void FillDataSet(ref DistanceDataTableAdapter taD, ref MyDataSet ds) {
  using (var myMRE = new ManualResetEventSlim(false)) {
    ds.EnforceConstraints = false;
    ds.Distance.BeginLoadData();
    Func<DistanceDataTable, int> distanceFill = taD.Fill;
    distanceFill.BeginInvoke(ds.Distance, FillCallback<DistanceDataTable>, new object[] { distanceFill, myMRE });
    WaitHandle.WaitAll(new []{ myMRE.WaitHandle });
    ds.Distance.EndLoadData();
    ds.EnforceConstraints = true;
  }
}
/// <summary>
/// Callback used when filling a table asynchronously.
/// </summary>
/// <param name="result">Represents the status of the asynchronous operation.</param>
private void FillCallback<MyDataTable>(IAsyncResult result) where MyDataTable: DataTable {
  var state = result.AsyncState as object[];
  Debug.Assert((state != null) && (state.Length == 2), "State variable is either null or an invalid number of parameters were passed.");

  var fillFunc = state[0] as Func<MyDataTable, int>;
  var mre = state[1] as ManualResetEventSlim;
  Debug.Assert((mre != null) && (fillFunc != null));
  int rowsAffected = fillFunc.EndInvoke(result);
  Debug.WriteLine(" Rows: " + rowsAffected.ToString());
  mre.Set();
}

возможно, ваш метод MessageReceived должен просто пометить значение свойства вашего интерфейса IChannel при реализации обработчика событий INotifyPropertyChanged, так что вам будет сообщено, когда свойство будет изменено.

таким образом, ваш класс отправителя может цикл, пока не истечет максимальное время ожидания, или всякий раз, когда PropertyChanged обработчик событий происходит, разрыв цикла успешно. Если ваш цикл не сломается, то сообщение будет считаться как никогда полученный.


полезный образец с AutoResetEvent:

    using System;
    using System.Threading;

    class WaitOne
    {
        static AutoResetEvent autoEvent = new AutoResetEvent(false);

        static void Main()
        {
            Console.WriteLine("Main starting.");

            ThreadPool.QueueUserWorkItem(
                new WaitCallback(WorkMethod), autoEvent);

            // Wait for work method to signal.
            autoEvent.WaitOne();
            Console.WriteLine("Work method signaled.\nMain ending.");
        }

        static void WorkMethod(object stateInfo) 
        {
            Console.WriteLine("Work starting.");

            // Simulate time spent working.
            Thread.Sleep(new Random().Next(100, 2000));

            // Signal that work is finished.
            Console.WriteLine("Work ending.");
            ((AutoResetEvent)stateInfo).Set();
        }
    }

WaitOne - это очень правильный инструмент для этой работы. Короче говоря, вы хотите подождать между 0 и MaxWaitInMs миллисекунды для выполнения задания. У вас действительно есть два варианта: опрос для завершения или синхронизация потоков с некоторой конструкцией, которая может ждать произвольное количество времени.

поскольку вы хорошо знаете правильный способ сделать это, для потомства я опубликую версию опроса:

MessageEventArgs msgArgs = null;
var callback = (object o, MessageEventArgs args) => {
    msgArgs = args;
};

_c.MessageReceived += callback;
_c.Send(m);

int msLeft = MaxWaitInMs;
while (msgArgs == null || msLeft >= 0) {
    Thread.Sleep(100);
    msLeft -= 100; // you should measure this instead with say, Stopwatch
}

_c.MessageRecieved -= callback;