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

本文关键字:计时器 问题 线程 | 更新日期: 2023-09-27 18:25:25

我正在开发一个类似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(负责显示传入的视频)由于我不知道但由计时器引起的某种原因未能完成其工作。

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

很遗憾,您的问题没有包括一个可靠地再现问题的好的最小完整代码示例。有这样一个代码示例将使提供有用答案的人更加实用。

也就是说,正如评论者varocarbas所解释的,你的根本问题似乎是你阻塞了UI线程(使用while循环),同时希望UI线程处理其他活动(例如计时器的滴答事件)。事实上,您也在阻止按钮点击产生效果。按钮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事件处理程序返回的值。即如果用户点击btnOk则为true,如果用户点击了btnNo则为false或计时器间隔已过。

请注意,这将更改ics_IncomingCall()方法的签名,这将强制对调用者进行更改。处理此问题的最佳方法是将调用者也更改为async并使用await ics_IncomingCall(...)。这当然会迫使更改其调用方和调用方的调用方,等等。但你需要释放UI线程,这是最好的方法。希望你没有太多的调用方需要更改,但即使你这样做了,这也是可行的。


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