3

情况1

这是我的设置。

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;
    }
}

我怎样才能等待这两个条件都成立

如果我做:

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

中的代码ApiStateHandler()似乎永远不会执行。

如果我做:

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

这可行,但似乎浪费资源和黑客攻击。

基本上我认为我需要一种方法来wait但不阻塞线程。这样做的正确方法是什么?

案例2

第二种情况有些相似(并且相关),并说明了相同的问题。

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;
    }
}

调用Send()时,Verifier会创建对象,方法需要等待ApiNotifyHandler执行(即ApiFound事件发生)后才能调用ApiSend()

所以这与案例 1中的情况相同。我应该如何等待 shouldContinue 为真

很抱歉这个很长的问题,但我想提供尽可能多的信息来帮助你帮助我。

[更新]

我被迫使用.Net 2.0。

4

3 回答 3

2

处理这个问题的最好方法是重构您的代码以使用async/await并将ApiStateUpdate事件转换为一个可等待的任务TaskCompletionSourceEAP 模式)。

如果您真的想在 UI 线程上同步等待事件,请WaitWithDoEvents这里CoWaitForMultipleHandles这里查看,他们就是这样做的。请记住,这种方法创建了一个嵌套的模式消息循环,可能的代码重入是最显着的含义(在此处详细讨论)。

[已编辑]您在这里尝试做的是异步到同步的桥接,这本身几乎总是一个坏主意。此外,我刚刚意识到您正在构造函数中执行此操作。构造函数本质上不应该有任何异步代码,它们是原子的。总有一种更好的方法可以将冗长的初始化过程从构造函数中分解出来。@StephenCleary 在他内容丰富的博文中谈到了这一点。

关于 .NET 2.0 的限制。虽然async/await可能是一个革命性的概念,但其背后的状态机概念并不是什么新鲜事。您始终可以使用一系列委托回调和事件来模拟它。自 .NET 2.0 以来,匿名代表就一直存在。例如,您的代码可能如下所示:

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;
    }
}

使用的客户端代码MyClass可能如下所示:

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

以上将是 .NET 2.0 的正确的基于异步事件的模式WaitWithDoEvents一个更简单但更糟糕的解决方案是使用(从这里,基于)来实现异步到同步桥MsgWaitForMultipleObjects,它可能看起来像这样:

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);
        }
    }
}

然而,这仍然比您的问题中的繁忙等待循环更有效:

while(!(this.cond1 && this.cond2))
{
    System.Windows.Forms.Application.DoEvents();
}
于 2013-10-31T02:44:50.783 回答
1

您将不得不异步执行阻塞代码。否则你会挂掉不好的 UI 线程。有多种不同的方法可以做到这一点。这是一个使用 newasyncawait关键字的方法。我事先承认这是很多东西,而且只在 .NET 4.5+ 中可行。

关于案例#1

首先,将您的 EAP(基于事件的异步模式)转换为 TAP(基于任务的异步模式)。这看起来很难看,因为 EAP 很难处理。您必须订阅该事件,然后在完成后取消订阅。

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;
} 

一旦你有了它,它就会像这样使用。

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.
    }
}

我建议您在执行此操作之前阅读Stephen Cleary关于异步初始化async的博客。await实际上,通读他所有的 Async OOP 系列。这真的很好。

关于案例#2

在很多方面,这种情况更容易处理,因为构造函数和对象初始化不会发挥作用。不过,您仍然需要使用与上述相同的策略。首先,将 API 的 EAP 样式转换为 TAP 样式。如果此等待条件不依赖于事件,ApiObject那么只需在 TAP 方法中使用您需要的任何等待逻辑并定期评估它。最好这将在不进行繁忙等待的情况下完成。然后await是您创建的 TAP 方法。不要忘记标记为何Sendasync执行此操作。

于 2013-10-31T03:05:34.387 回答
0

基本上我认为我需要一种等待但不阻塞线程的方法。

您确实想阻止线程,而不是 UI 线程。因此只需创建另一个线程并阻止它。让它变得简单并使用 BackgroundWorker() 控件。

于 2013-10-31T01:49:15.777 回答