How to wait for signal in WinForms while also listening to events?

人走茶凉 提交于 2020-05-22 09:20:27

问题


Case 1

Here is my setup.

internal class MyClass
{
    private ApiObject apiObject;

    private bool cond1;
    private bool cond2;

    internal MyClass()
    {
        this.apiObject = new ApiObject();
        this.apiObject.ApiStateUpdate += new ApiStateUpdateEventHandler(ApiStateHandler);

        //wait for both conditions to be true
    }

    private void ApiStateHandler(string who, int howMuch)
    {
        if(who.Equals("Something") && howMuch == 1)
            this.cond1 = true;
        else if(who.Equals("SomethingElse") && howMuch == 1)
            this.cond2 = true;
    }
}

How can I wait for both conditions to be true?

If I do:

while(!(this.cond1 && this.cond2))
{
    System.Threading.Thread.Sleep(1000);
}

the code in the ApiStateHandler() never seems to execute.

If I do:

while(!(this.cond1 && this.cond2))
{
    System.Windows.Forms.Application.DoEvents();
}

this works but seems like a waste of resources and a hack.

Basically I think I need a way to wait but without blocking the Thread. What's the proper of way of doing this?

Case 2

The second case is somewhat similar (and related) and illustrates the same problem.

internal class MyClass
{
    private ApiNotifyClass apiNotifyClass;
    private shouldContinue = false;

    internal MyClass()
    {
        //in addition to the code from above
        this.apiNotifyClass = new ApiNotifyClass();
        this.apiNotifyClass.ApiFound += ApiNofityFoundEventHandler(ApiNotifyHandler);
    }

    internal void Send(SomethingToSend somethigToSend)
    {
        Verifyer verifier = this.apiObject.ApiGet(somethingToSend);
        this.apiNotifyClass.ApiAttach(verifier);

        //wait for the shouldContinue to be true

        this.apiObject.ApiSend(verifier);

        this.apiNotifyClass.ApiDetach(verifier);
    }

    private void ApiNotifyHandler()
    {
        this.shouldContinue = true;
    }
}

When calling Send(), the Verifier object will be created and the method needs to wait for the ApiNotifyHandler to execute (i.e., the ApiFound event to occur) before calling the ApiSend().

So this is the same situation as is in Case 1. How should I wait for the shouldContinue to be true?

Sorry for the very long question, but I thought of giving as much information as possible to help you help me.

[Update]

I'm forced to use .Net 2.0.


回答1:


The best way to deal with this is to re-factor your code to use async/await and turn ApiStateUpdate event into an awaitable task with TaskCompletionSource (EAP pattern).

If your really want to wait synchronously for an event on a UI thread, look at WaitWithDoEvents from here or CoWaitForMultipleHandles from here, they do just that. Keep in mind, this approach creates a nested modal message loop, with possible code re-entrancy being the most notable implication (discussed in details here).

[EDITED] What you're trying to do here is an asynchronous-to-synchronous bridge, which is almost always a bad idea on its own. Moreover, I just realized you're doing this in a constructor. Constructors, by their nature, should not have any asynchronous code inside, they're atomic. There is always a better way to factor a lengthy initialization procedure out of the constructor. @StephenCleary talks about this in his very informative blog post.

Regarding .NET 2.0 restrictions. While async/await might be a revolutionary concept, the state machine concept behind it is nothing new. You can always simulate it with a chain of delegate callbacks and events. Anonymous delegates has been there since .NET 2.0. For example, your code might look like this:

internal class MyClass
{
    private ApiObject apiObject;

    public event EventHandler Initialized;

    internal MyClass()
    {
        this.apiObject = new ApiObject();
    }

    public void Initialize()
    {
        ApiStateUpdateEventHandler handler = null;

        handler = delegate(string who, int howMuch) 
        {
            bool cond1 = false;
            bool cond2 = false;

            if(who.Equals("Something") && howMuch == 1)
                cond1 = true;
            else if(who.Equals("SomethingElse") && howMuch == 1)
                cond2 = true;           

            //wait for both conditions to be true

            if ( !cond1 && !cond2 )
                return;

            this.apiObject.ApiStateUpdate -= handler;

            // fire an event when both conditions are met
            if (this.Initialized != null)
                this.Initialized(this, new EventArgs());
        };

        this.apiObject.ApiStateUpdate += handler;
    }
}

The client code using MyClass might look like this:

MyClass myObject = new MyClass();
myObject.Initialized += delegate 
{
    MessageBox.Show("Hello!"); 
};
myObject.Initialize();

The above would be the proper asynchronous event-based pattern for .NET 2.0. An easier, but worse solution would be to implement the asynchronous-to-synchronous bridge, using WaitWithDoEvents (from here, based on MsgWaitForMultipleObjects), which may look like this:

internal class MyClass
{
    private ApiObject apiObject;

    internal MyClass()
    {
        this.apiObject = new ApiObject();
        Initialize();
    }

    private void Initialize()
    {
        using (ManualResetEvent syncEvent = new ManualResetEvent())
        {
            ApiStateUpdateEventHandler handler = null;

            handler = delegate(string who, int howMuch) 
            {
                bool cond1 = false;
                bool cond2 = false;

                if(who.Equals("Something") && howMuch == 1)
                cond1 = true;
                else if(who.Equals("SomethingElse") && howMuch == 1)
                    cond2 = true;           

                //wait for both conditions to be true

                if ( !cond1 && !cond2 )
                    return;

                this.apiObject.ApiStateUpdate -= handler;

                syncEvent.Set();
            };

            this.apiObject.ApiStateUpdate += handler;
            WaitWithDoEvents(syncEvent, Timeout.Infinite);
        }
    }
}

Yet that'd be still more efficient than a busy waiting loop from your question:

while(!(this.cond1 && this.cond2))
{
    System.Windows.Forms.Application.DoEvents();
}



回答2:


You are going to have to execute the blocking code asynchronously. Otherwise you would hang up the UI thread which is not good. There are various different ways to do this. Here is one that uses the new async and await keywords. I admit right up front that this is a lot to swallow and is only feasible in .NET 4.5+.

Regarding Case #1

First, convert your EAP (event-based asynchronous patter) into a TAP (task-based asynchronous pattern). This looks ugly because EAP is hard to deal with. You have to subscribe to the event and then unsubscribe when you are done with it.

private Task<ApiObject> CreateApiObjectAsync()
{
  bool cond1 = false;
  bool cond2 = false;

  var tcs = new TaskCompletionSource<ApiObject>();

  ApiObject instance = null;
  ApiStateEventHandler handler = null;

  handler = (who, howmuch) =>
    {
      cond1 = cond1 || (who == "Something" && howmuch == 1);
      cond2 = cond2 || (who == "SomethingElse" && howmuch == 1);
      if (cond1 && cond2)
      {
        instance.ApiStateUpdate -= handler;
        tcs.SetResult(instance);
      }
    }

  var instance = new ApiObject();
  instance.ApiStateUpdate += handler;
  return tcs.Task;
} 

Once you have that in place then it would be used like this.

internal class MyClass
{
    private ApiObject apiObject;

    internal MyClass()
    {
      InitializeAsync();
    }

    private async Task InitializeAsync()
    {
      apiObject = await CreateApiObjectAsync();
      // At this point the instance is created and fully initialized.
    }
}

I recommend that you read Stephen Cleary's blog on asynchronous initialization using async and await before you do this though. Actually, read through all of his Async OOP series. It is really good.

Regarding Case #2

In a lot of ways this case is easier to deal with because constructors and object initialization do not come into play. You will still need to use the same strategy as above though. First, convert the API's EAP style into the TAP style. If this wait condition is not dependent on an event from ApiObject then just right whatever wait logic you need in the TAP method and periodically evaluate it. Preferably this would be done without doiing a busy wait. Then await the TAP method you created. Do not forget to mark Send as async when doing this though.




回答3:


Basically I think I need a way to wait but without blocking the Thread.

You do want to block the thread, just not the UI thread. Therefore simply create another thread and block that. Make it easy and use the BackgroundWorker() control.



来源:https://stackoverflow.com/questions/19696649/how-to-wait-for-signal-in-winforms-while-also-listening-to-events

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!