C#中事件订阅者的奇怪执行

时间:2014-03-27 07:35:30

标签: c# events user-controls delegates event-handling

我有一个事件及其方法声明如下,它是一个Windows窗体登录控件的身份验证事件:

public event EventHandler<AuthenticateEventArgs> Authenticate;

protected void OnAuthenticate(AuthenticateEventArgs e)
{
    EventHandler<AuthenticateEventArgs> handler = Authenticate;
    if (handler != null)
    {
        handler(this, e);
    }

    if (e.Authenticated)
    {
        OnLoggedIn(new EventArgs());
    }
        else
    {
        OnLoggedError(new EventArgs());
    }
}

点击按钮会引发事件,现在假设在其他项目中有此事件的订阅者,如下所示:

this.loginControl1.Authenticate += loginControl1_Authenticate;
this.loginControl1.Authenticate += delegate(object o, AuthenticateEventArgs ea)
        {
            System.Threading.Thread.Sleep(2000);
            ea.Authenticated = false;
        };
this.loginControl1.Authenticate += delegate(object o, AuthenticateEventArgs ea)
        {
            System.Threading.Thread.Sleep(2000);
            ea.Authenticated = true;
        };
this.loginControl1.Authenticate += delegate(object o, AuthenticateEventArgs ea)
        {
            System.Threading.Thread.Sleep(2000);
            ea.Authenticated = false;
        };

System.Threading.Thread.Sleep(2000);只是需要一些时间的某个过程的模拟。问题是最后一个订户在OnAuthenticate方法中执行If条件并引发另一个事件,而订阅者之前则没有。代码适用于一个用户。在这种情况下问题在哪里?

4 个答案:

答案 0 :(得分:1)

由于您希望异步执行身份验证方法,因此可以像这样工作。

创建一个返回bool

的委托

public delegate bool Authenticate(object sender, AuthenticateEventArgs e);

Authenticate authHandler;

您可能会也可能不会使用这些参数,但您可以稍后使用或删除它。

创建您的身份验证方法

bool AuthenticationMethod1(object o, AuthenticateEventArgs ea)
{
    System.Threading.Thread.Sleep(2000); //Simulate some long running task.
    return false; //Return true or false based on authentication failed or succeeded.
}

bool AuthenticationMethod2(object o, AuthenticateEventArgs ea)
{
    System.Threading.Thread.Sleep(2000); //Simulate some long running task.
    return true; //Return true or false based on authentication failed or succeeded.
}

bool AuthenticationMethod3(object o, AuthenticateEventArgs ea)
{
    System.Threading.Thread.Sleep(2000); //Simulate some long running task.
    return false; //Return true or false based on authentication failed or succeeded.
}

连接处理程序

authHandler += AuthenticationMethod1;
authHandler += AuthenticationMethod2;
authHandler += AuthenticationMethod3;

现在执行

if (authHandler != null)
{
    foreach (Authenticate handler in authHandler.GetInvocationList())
    {
        handler.BeginInvoke(this, e as AuthenticateEventArgs, new AsyncCallback(Callback), handler);
    }                
}

上一篇:您是否回调定义了

void Callback(IAsyncResult ar)
{
    Authenticate d = (Authenticate)ar.AsyncState;
    if (d.EndInvoke(ar))
    {
       OnLoggedIn(new EventArgs());
    }
    else
    {
       OnLoggedError(new EventArgs());
    }
}

答案 1 :(得分:0)

这是众所周知的行为,事件按订阅顺序执行,因此最后订阅的订阅者将覆盖所有先前的值。您可以看到最后一个订阅者的更新(在这种情况下为false)。

您可以通过检查已经过身份验证并跳过处理来解决此问题。

this.loginControl1.Authenticate += delegate(object o, AuthenticateEventArgs ea)
{
    if(ea.Authenticated)
    {
        return;
    }
    System.Threading.Thread.Sleep(2000);
    ea.Authenticated = false;
};
this.loginControl1.Authenticate += delegate(object o, AuthenticateEventArgs ea)
{        
    if(ea.Authenticated)
    {
        return;
    }
    System.Threading.Thread.Sleep(2000);
    ea.Authenticated = true;
};
this.loginControl1.Authenticate += delegate(object o, AuthenticateEventArgs ea)
{
    if(ea.Authenticated)
    {
        return;
    }
    System.Threading.Thread.Sleep(2000);
    ea.Authenticated = false;
};

原因是因为您看到Authenticated false始终为handler(this, e);,只有在调用所有已订阅的方法后才会返回。因此,您的上一个订阅者将Authenticated设置为false,因此只有在if (e.Authenticated)执行时才会看到错误。

答案 2 :(得分:0)

通过以下代码删除事件:

this.loginControl1.Authenticate -= loginControl1_Authenticate;

答案 3 :(得分:0)

问题是所有事件订阅者将在与触发它们的函数相同的线程上执行,以便执行到达时

        handler(this, e);

执行将移至第一个订户代码

        System.Threading.Thread.Sleep(2000);
        ea.Authenticated = false;

然后执行转到第二个订阅者。最后,它将执行最后一个订户代码,然后执行回到调用函数

protected void OnAuthenticate(AuthenticateEventArgs e)

并且执行从事件触发器行继续到第二个if语句,并且此时ea.Authenticated的值为false,因为上一个订阅者设置它。

如果您想在不同的帖子上提出每个事件,请检查Trigger events on separated threads