带有线程的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(负责显示传入的视频)由于我不知道但由计时器引起的某种原因未能完成其工作。
很遗憾,您的问题没有包括一个可靠地再现问题的好的最小、完整代码示例。有这样一个代码示例将使提供有用答案的人更加实用。
也就是说,正如评论者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是完全和最小。您将希望从示例中删除任何不严格要求重现问题的代码。同时,确保有人可以将代码复制并粘贴到一个空项目中,并使其运行时最多只需非常小的工作量,最好根本不需要。