带有线程的C#中的计时器问题

时间:2015-12-03 11:57:03

标签: c# multithreading timer

我正在开发一个类似skype的应用程序,我有一个外部DLL来完成大部分工作并触发我的类ip2ip中处理的事件,其中一个事件是当有一个来电时,就像名字所暗示的那样触发了incoming_call。我试图管理未接来电。 现在这是这个类中代码的相关部分:

    private void ics_IncomingCall(object sender, string authenticationData, int socketHandle, string callbackid, string callbackipaddress, int callbackvideoport, int callbackaudiotcpport, int callbackaudiudpport)
    {
        if (Calling)
        {
            ics.RejectCall("The contact have another call", (IntPtr)socketHandle);
            Message = "An incoming call from [" + callbackipaddress + "] has rejected.";
        }
        else
        {
            AcceptIncomingCall = null;

            UserCaller = FindUserName(callbackipaddress);

            IncomingCall = true;

            //waiting for the call to be accepted from outside of this class
            while (AcceptIncomingCall.HasValue == false) Thread.Sleep(100);

            if(AcceptIncomingCall.Value == true)
            {
                //call back to have a 1 on one video conference

                icc.Parent.BeginInvoke(new MethodInvoker(delegate
                {
                    //accept the incoming call

                    ics.AcceptCall("n/a", socketHandle);

                    icc.Call(callbackipaddress, callbackvideoport, 0, 0,
                        "n/a", callbackid,
                        ics.GetLocalIp()[0].ToString(), 0, 0, 0, "");

                    Calling = true;
                }));
            }
            else
            {
                ics.RejectCall("Call not accepted", (IntPtr)socketHandle);
                Log = "Incoming call not accepted";

                Calling = false;
            }

            AcceptIncomingCall = null;

            IncomingCall = false;
        }

    }

IncomingCall是一个生成PropertyChangedEvent的属性,它在我的主类中被捕获,我有这个代码:

    private void ip2ip_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e != null && string.IsNullOrEmpty(e.PropertyName) == false)
        {

        ..............
            if (e.PropertyName.Equals("IncomingCall") && ip2ip.IncomingCall == true)
            {
                Invoke(new MethodInvoker(delegate 
                    { 
                        pnlCalling.Visible = true;
                        aTimer.Start();
                    }));
            }
         ................

        }
    }

    public Form1()
    {
        .......
        aTimer = new System.Windows.Forms.Timer();
        aTimer.Interval = 10000;
        aTimer.Tick += aTimer_Tick;
    }

    void aTimer_Tick(object sender, EventArgs e)
    {
        aTimer.Stop();
        btnNo.PerformClick();
    }
    private void btnNo_Click(object sender, EventArgs e)
    {
        aTimer.Stop();
        ip2ip.AcceptIncomingCall = false;
    }

    private void btnOk_Click(object sender, EventArgs e)
    {
        aTimer.Stop();
        ip2ip.AcceptIncomingCall = true;
    }

我需要定时器来管理未接来电,当有来电时会出现一个面板,并带有接听/拒绝来电的按钮。如果用户等待太多,则认为呼叫被拒绝(错过)。 通过这种方式,它不起作用,可能是我对计时器做错了,因为没有任何计时器一切正常。我也尝试了类System.Timers的计时器,结果相同。有什么想法吗?

修改

这是我的期望,有一个传入的调用所以事件ics_IncomingCall被触发,IncomingCall=true导致执行进入主类(我们仍然在同一个线程中,我看到它调试在VS)中一步一步地在GUI线程中调用面板可见并启动计时器,现在我们有一个线程,其中while循环阻止执行,直到在另一个线程用户做某事(接受/拒绝)。 当用户接受调用时,存在问题,while循环后的代码总是被执行,调用者完全没有问题并且接收流,但是在接收者(我在wireshark中验证的人接收流)DLL(谁负责显示传入的视频)由于我不知道但由计时器引起的某些原因而无法完成其工作。

1 个答案:

答案 0 :(得分:0)

遗憾的是,您的问题不包括可靠地重现问题的a good, minimal, complete code example。拥有这样的代码示例会使某人提供有用的答案更加实用。

如上所述,正如评论者varocarbas所解释的那样,您的基本问题似乎是您已经阻止了UI线程(使用while循环),同时希望获得UI线程处理其他活动(例如计时器的tick事件)。实际上,您也在阻止按钮单击产生效果。按钮Click事件处理程序也无法执行,而UI线程被阻止。

解决此问题的一种方法是使用TaskCompletionSource<T>ics_IncomingCall()提供一个可等待的对象,按钮和计时器可以用来发出信号。例如:

// Change from "bool?" to this:
private TaskCompletionSource<bool> AcceptIncomingCall;

public void HandleCall(bool accept)
{
    AcceptIncomingCall.SetResult(accept);
}

private async Task ics_IncomingCall(object sender, string authenticationData, int socketHandle, string callbackid, string callbackipaddress, int callbackvideoport, int callbackaudiotcpport, int callbackaudiudpport)
{
    if (Calling)
    {
        ics.RejectCall("The contact have another call", (IntPtr)socketHandle);
        Message = "An incoming call from [" + callbackipaddress + "] has rejected.";
    }
    else
    {
        AcceptIncomingCall = new TaskCompletionSource<bool>();

        UserCaller = FindUserName(callbackipaddress);

        IncomingCall = true;

        //waiting for the call to be accepted from outside of this class
        if (await AcceptIncomingCall.Task)
        {
            //call back to have a 1 on one video conference

            icc.Parent.BeginInvoke(new MethodInvoker(delegate
            {
                //accept the incoming call

                ics.AcceptCall("n/a", socketHandle);

                icc.Call(callbackipaddress, callbackvideoport, 0, 0,
                    "n/a", callbackid,
                    ics.GetLocalIp()[0].ToString(), 0, 0, 0, "");

                Calling = true;
            }));
        }
        else
        {
            ics.RejectCall("Call not accepted", (IntPtr)socketHandle);
            Log = "Incoming call not accepted";

            Calling = false;
        }

        AcceptIncomingCall.Dispose();

        IncomingCall = false;
    }

}

void aTimer_Tick(object sender, EventArgs e)
{
    aTimer.Stop();
    btnNo.PerformClick();
}

private void btnNo_Click(object sender, EventArgs e)
{
    aTimer.Stop();
    genericServerClient.HandleCall(false);
}

private void btnOk_Click(object sender, EventArgs e)
{
    aTimer.Stop();
    genericServerClient.HandleCall(false);
}

这导致ics_IncomingCall()方法在到达await语句时返回,允许其线程继续执行。按钮Click事件处理程序将回调封装您的字段的公共方法(公共字段非常危险,几乎在所有情况下都应该避免),设置TaskCompletionSource对象的结果值等待着。

一旦设置了结果值,这将导致框架继续执行它停止的ics_IncomingCall()方法,但现在使用按钮Click事件处理程序返回的值。即true如果用户点击了btnOkfalse,则会点击btnNo或定时器间隔时间。

请注意,这会更改ics_IncomingCall()方法的签名,这将强制更改调用方。处理这种情况的最佳方法是更改​​调用者,使用async并使用await ics_IncomingCall(...)。这当然会强制改变调用者及其调用者的调用者,等等。但是你需要释放UI线程,这是最好的方法。希望你没有很多呼叫者可以改变,但即使你这样做,这也是要走的路。


如果以上似乎无法解决您的问题,请提供一个好的MCVE。请注意,良好的MCVE是完整最小。您将需要从示例中删除任何不是严格要求重现问题的代码。同时,确保有人可以将代码复制并粘贴到一个空项目中,并使其最多只运行非常小的,最好不运行。