两个计时器导致WinForms应用程序中的冲突

gijlo24d  于 2023-01-02  发布在  其他
关注(0)|答案(2)|浏览(162)

我在WinForms应用程序中有两个计时器(System.Windows.Forms.Timer)。
两个计时器同时启动。一个计时器在程序更新三个标签的整个生命周期中启动和停止,另一个计时器只在每次tick事件更新一个标签时运行和完成其工作。然而,当第一个计时器在其tick事件中运行代码时,第二个计时器不运行。
在第一个计时器tick事件代码中,我插入了多个System.Threading.Thread.Yield();语句,但是第二个计时器仍然被阻塞,搜索结果为空。
我试着为第二个计时器使用系统线程,但它什么也没做。
我不知道。有什么主意吗?

public partial class fMain2 : Form
{
    private System.Windows.Forms.Timer timer;
    private System.Windows.Forms.Timer timer2;
    private Thread tThread;
    
    private int totTime;
    private int curTime;
    private int exTime = 0;
    public int runTime = 0;
    
    private void cmdRun_Click(object sender, EventArgs e)
    {
        //calculate total time
        totTime = iCount * iDuration;
        lblTotTime.Text = totTime.ToString();
        lblTotEx.Text = exTime.ToString();
        System.Threading.Thread.Yield();

        curTime = int.Parse("0" + txtDuration.Text);
        
        System.Threading.Thread.Yield();

        this.Refresh();

        strFile = "Begin" + ".wav";
        snd.SoundLocation = strSoundFilePath + strFile;
        snd.PlaySync();
        
        //select first item in the listview
        lvTimer.Items[0].Selected = true;
        lvi = lvTimer.Items[0];
        lvTimer.Refresh();
        
        strFile = lvi.SubItems[1].Text + ".wav";
        snd.SoundLocation = strSoundFilePath + strFile;
        snd.PlaySync();
        System.Threading.Thread.Yield();

        strFile = "Go" + ".wav";
        snd.SoundLocation = strSoundFilePath + strFile;
        snd.PlaySync();
        
        //attempted using a thread for timer2
        timer.Start();
        //tThread = new Thread(new ThreadStart(timer2.Start));

        timer2.Start();
        //tThread.Start();
    }
        
    private void timerTick(object sender, EventArgs e)
    {
        string strFile;
        
        curTime -= 1;
        totTime -= 1;
        
        exTime += 1;

        System.Threading.Thread.Yield();

        lblCurTime.Text = curTime.ToString();
        lblTotTime.Text = totTime.ToString();
        lblTotEx.Text = exTime.ToString();
        this.Refresh();
        
        System.Threading.Thread.Yield();

        if (curTime == 0)
        {
            timer.Stop();

            System.Threading.Thread.Yield();
            System.Threading.Thread.Yield();

            strFile = "Stop" + ".wav";
            snd.SoundLocation = strSoundFilePath + strFile;
            snd.PlaySync();

            System.Threading.Thread.Yield();
            System.Threading.Thread.Yield();
            
            if (totTime == 0)
            {
                //this marks the end of the program
                timer2.Stop();
                //tThread.Abort();
                
                //more code but not relevant
                return;
            }
            else
            { //we are still working down the listview
                
                try
                {
                    lvi = lvTimer.Items[lvi.Index + 1];
                    lvTimer.Items[lvi.Index].Selected = true;
                    lvTimer.FocusedItem = lvTimer.Items[lvi.Index];
                    lvTimer.Refresh();
                    System.Threading.Thread.Yield();
                    System.Threading.Thread.Yield();
                    System.Threading.Thread.Yield();
                }
                catch (IndexOutOfRangeException ei)
                {
                    strFile = "End" + ".wav";
                    snd.SoundLocation = strSoundFilePath + strFile;
                    snd.PlaySync();
                    bRunning = false;
                    ResetTime();
                    return;
                }

                curTime = int.Parse("0" + txtDuration.Text);

                lblCurTime.Text = curTime.ToString();
                lblTotTime.Text = totTime.ToString();

                System.Threading.Thread.Yield();
                System.Threading.Thread.Yield();
                
                //I'm wondering if the soundplayer is causing the problem
                strFile = lvi.SubItems[1].Text + ".wav";
                snd.SoundLocation = strSoundFilePath + strFile;
                System.Threading.Thread.Yield();
                System.Threading.Thread.Yield();

                snd.PlaySync();

                System.Threading.Thread.Yield();
                System.Threading.Thread.Yield();
                
                strFile = "Go" + ".wav";
                snd.SoundLocation = strSoundFilePath + strFile;
                snd.PlaySync();
                
                System.Threading.Thread.Yield();
                System.Threading.Thread.Yield();
                
                System.Threading.Thread.Yield();
                
                timer.Start();
            }
        }
    }
    
    private void timer2Tick(object sender, EventArgs e)
    {
        //this is all timer2 does.  It runs as long as the 
        //  program is running.
        runTime += 1;
        lblTotTotal.Text = (runTime / 60).ToString()
            + ":" + (runTime % 60).ToString("00");
    }
}

我正在使用VS 2017。

5kgi1eie

5kgi1eie1#

System.Windows.Forms.Timer设计用于在UI线程上运行代码。调用System.Threading.Thread.Yield()会告诉系统运行另一个已准备好在当前内核上运行的线程,但您的计时器希望在UI线程上运行,因此它在任何方面都没有帮助。
在我看来,您正在阻止UI线程使用.PlaySync()播放您的声音。
我建议您将声音的播放推送到后台线程,以释放UI。
根据我从你的代码中收集到的信息,你正在尝试播放一系列类似这样的声音:

"Begin.wav", then "ListItem1.wav", then"Go.wav"
(wait a period of time)
"ListItem2.wav", then "Go.wav"
(wait a period of time)
"ListItem3.wav", then "Go.wav"
(wait a period of time)
"End.wav"

但是,如果计时器超时,则取消播放这些声音,并播放"Stop.wav"声音。
这个结构可以用Queue<Queue<string>>建模,您只需要执行嵌套的出队操作并支付所有声音。

Queue<Queue<string>> queue =
    new Queue<Queue<string>>(new[]
    {
        new Queue<string>(new[] { "Begin.wav", "ListItem1.wav", "Go.wav" }),
        new Queue<string>(new[] { "ListItem2.wav", "Go.wav" }),
        new Queue<string>(new[] { "ListItem3.wav", "Go.wav" }),
        new Queue<string>(new[] { "End.wav" }),
    });

下面是将队列出队的代码:

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;

Task task = Task.Run(async () =>
{
    while (queue.Any())
    {
        if (ct.IsCancellationRequested)
        {
            break;
        }
        Queue<string> inner = queue.Dequeue();
        while (inner.Any())
        {
            if (ct.IsCancellationRequested)
            {
                break;
            }
            string soundLocation = inner.Dequeue();
            using (System.Media.SoundPlayer sp = new System.Media.SoundPlayer())
            {
                sp.SoundLocation = soundLocation;
                sp.PlaySync();
            }
        }
        if (ct.IsCancellationRequested)
        {
            using (System.Media.SoundPlayer sp = new System.Media.SoundPlayer())
            {
                sp.SoundLocation = soundLocationStop;
                sp.PlaySync();
            }
        }
        else
        {
            await Task.Delay(TimeSpan.FromSeconds(2.0));
        }
    }
});

请注意,这些都是在Task.Run中运行的,因此不会阻塞UI线程。
要停止处理声音,只需调用cts.Cancel();
现在只需要在cmdRun_Click方法中构建队列。

private void cmdRun_Click(object sender, EventArgs e)
{
    string[][] soundLocationGroups =
    (
        from x in lvTimer.Items.Cast<ListViewItem>()
        from y in x.SubItems.Cast<ListViewItem.ListViewSubItem>()
        select new[] { Path.Combine(strSoundFilePath, $"{y.Text}.wav"), Path.Combine(strSoundFilePath, $"Go.wav") }
    ).ToArray();
        
    soundLocationGroups =
        soundLocationGroups
            .Take(1)
            .Select(xs => xs.Prepend(Path.Combine(strSoundFilePath, $"Begin.wav")).ToArray())
            .Concat(soundLocationGroups.Skip(1))
            .Append(new[] { Path.Combine(strSoundFilePath, $"End.wav") })
            .ToArray();
            
    string soundLocationStop = Path.Combine(strSoundFilePath, $"Stop.wav");             

    Queue<Queue<string>> queue = new Queue<Queue<string>>(soundLocationGroups.Select(x => new Queue<string>(x)));

您仍然需要一个计时器来确定是否应该调用cts.Cancel()
我在发布之前测试了我的Task.Run代码。它工作正常。

mccptt67

mccptt672#

我的建议是使用async/await

**步骤1:**删除所有System.Threading.Thread.Yield();调用。
**步骤2:**删除所有.Refresh();调用。
**步骤3:**将timerTickasyncprivate async void timerTick(object sender, EventArgs e)
**步骤4:**将每次出现的snd.PlaySync();替换为await Task.Run(() => snd.PlaySync());

Task.Run方法调用ThreadPool上指定的lambda。await允许当前线程在lambda在ThreadPool上运行时继续执行其他操作,如响应用户输入。lambda完成后,当前线程继续执行await之后的代码,直到下一个await。或者直到处理程序结束。
完成这些更改后,UI应再次响应,并且其他计时器应每秒正常计时。
请注意,通过将timerTick处理程序设置为async,现在可以以重叠的方式调用它,您可以通过检查和更新表单的字段来防止重叠。

相关问题