【问题标题】:C# Ui freezes when calling a method调用方法时 C# Ui 冻结
【发布时间】:2018-01-05 12:03:36
【问题描述】:

我有一个名为 Connect() 的函数,这个函数大约需要 2-3 秒,因为它使用了一些 api 请求。现在我想找到一种方法,让我的 UI 在启动此功能时不会冻结。

    private void connectToolStripMenuItem_Click(object sender, EventArgs e)
    {
        Connect() // << this tooks a lot of time
    }

我已经尝试用线程解决它

    private void connectToolStripMenuItem_Click(object sender, EventArgs e)
    {
        new Thread(Connect).Start();
    }

和一个后台工作人员

    private void backgroundWorkerConnect_DoWork(object sender, DoWorkEventArgs e)
    {
        Connect();
    }

但程序仍然冻结。

   private void Connect()
    {
        if (InvokeRequired)
        {
            Invoke(new MethodInvoker(Connect));
        }
        else
        {
            if (!connected)
            {
                connected = true;
                verbindenToolStripMenuItem.Enabled = false;
                trennenToolStripMenuItem.Enabled = true;
                InfoStripStatus.Text = "Status: Connected";

                irc.joinRoom(channel, BotConnectingMessage);
                chatThread = new Thread(getMessage);
                chatThread.Start();
                loadLoyalty();
                updateTimer = new System.Threading.Timer(timerViewer, null, 0, 60000);
            }
        }
    }

也许我只是做错了什么,希望有人能帮助我。

【问题讨论】:

  • 启动一个新线程不应该冻结 UI,因为它有自己的线程
  • Connect(); 方法不依赖或调用 UI 线程上的任何内容?如果将Connect() 中的任何内容替换为System.Threading.Thread.Sleep(3000);,它仍然会冻结用户界面吗? (使用最后两个选项进行测试)
  • 好吧,我尝试用 sleep(3000) 替换 Connect() 内容,现在不再冻结。但我不明白为什么,因为在 Connect() 功能中没有什么需要那么长时间。我将在顶部添加 Connect() 的内容:)
  • 好的,你能给我举个例子吗...PLEEAAASSEEE :D
  • 这是一个很好的示例,介绍如何使用后台工作程序以及如何在执行长时间操作时保持 UI 响应。 msdn.microsoft.com/fr-fr/library/cc221403(v=vs.95).aspx 如果您需要从后台线程更新 UI 控件,您应该使用它们的“Invoke”方法更新控件。

标签: c# function methods freeze


【解决方案1】:

使用另一个线程(无论是通过BackgroundWorker 还是直接创建一个线程)来调用一个方法,该方法只不过是在 UI 线程上调用一些代码然后等待它,什么也解决不了。你关心的代码仍然在 UI 线程中执行,阻塞它。

您应该使用async/awaitTask.Run() 来处理您的工作:

private async void connectToolStripMenuItem_Click(object sender, EventArgs e)
{
    await Connect();
}

private async Task Connect()
{
    if (!connected)
    {
        connected = true;
        verbindenToolStripMenuItem.Enabled = false;
        trennenToolStripMenuItem.Enabled = true;
        InfoStripStatus.Text = "Status: Connected";

        await Task.Run(() => irc.joinRoom(channel, BotConnectingMessage));
        chatThread = new Thread(getMessage);
        chatThread.Start();
        loadLoyalty();
        updateTimer = new System.Threading.Timer(timerViewer, null, 0, 60000);
    }
}

根据loadLoyalty() 的速度,您可能还需要await Task.Run(loadLoyalty); 而不是直接调用它。

上面将执行它所属的 UI 线程中的所有代码,除了您通过 Task.Run() 调用的代码。

还有其他方法可以重构代码,包括与BackgroundWorker 一起使用的替代方法(即仅使用Control.Invoke() 执行前四个语句,然后直接在Connect() 方法中运行其余语句)。但是恕我直言,上面使用async/await 是今天的最佳选择。

【讨论】:

  • 在阅读了一些关于 asyn/await 和 Task 的信息后,感谢您的配合,只需进行一些更改即可工作
【解决方案2】:

好吧,这看起来很有趣。我会尝试将其转移到其他函数,因为我发现是 updateTimer 冻结了它。

    private void timerViewer(object state)
    {
        irc.sendChatMessage("/mods");
        UpdateStream();
        UpdateChatters();
    } 

    private void UpdateStream()
    {
        if (InvokeRequired)
        {
            Invoke(new MethodInvoker(UpdateStream));
        }
        else
        {
            StreamInformations = TwitchROClient.getStream(TwitchROClient.getIDbyUsername("xzaliax"));

            if (StreamInformations.stream != null)
            {

                viewers = StreamInformations.stream.Viewers;
                totalviews = StreamInformations.stream.channel.Views;

                if (followers == 0)
                {
                    followers = StreamInformations.stream.channel.Followers;
                }
                else
                {
                    if (followers < StreamInformations.stream.channel.Followers)
                    {
                        newFollower();
                    }
                    followers = StreamInformations.stream.channel.Followers;
                }


                InfoStripViewer.Text = "|  " + string.Format(CultureInfo.InvariantCulture, "{0:N0}", viewers).Replace(',', '.') + " :Viewer";
                InfoStripFollower.Text = "|  " + string.Format(CultureInfo.InvariantCulture, "{0:N0}", followers).Replace(',', '.') + " :Follower ";
                InfoStripTotalViewer.Text = "|  " + string.Format(CultureInfo.InvariantCulture, "{0:N0}", totalviews).Replace(',', '.') + " :Total Viewers";
                InfoStripStream.Text = "|  Stream: Online";
            }
            else
            {
                InfoStripViewer.Text = "|  -- :Viewer";
                InfoStripFollower.Text = "|  -- :Follower";
                InfoStripTotalViewer.Text = "|  -- :Total Viewers";
                InfoStripStream.Text = "|  Stream: Offline";
            }
        }
    }
    private void UpdateChatters()
    {
        if (InvokeRequired)
        {
            Invoke(new MethodInvoker(UpdateChatters));
        }
        else
        {
            ChannenlChatters = TwitchROClient.getChatters(channel);
            lbViewer.Items.Clear();

            if (ChannenlChatters != null)
            {
                if (ChannenlChatters.AllChatters != null)
                {
                    tbChat.Text += "Checking the viewer list..." + Environment.NewLine;
                    if (ChannenlChatters.AllChatters.Admins.Count >= 0) lbViewer.Items.Add("_____________Admins_____________");
                    foreach (string admin in ChannenlChatters.AllChatters.Admins)
                    {
                        lbViewer.Items.Add(String.Format("{0,5}", admin));
                    }
                    if (ChannenlChatters.AllChatters.Admins.Count >= 0) lbViewer.Items.Add("");

                    if (ChannenlChatters.AllChatters.Staff.Count >= 0) lbViewer.Items.Add("_____________Stuff______________");
                    foreach (string stuff in ChannenlChatters.AllChatters.Staff)
                    {
                        lbViewer.Items.Add(String.Format("{0,5}", stuff));
                    }
                    if (ChannenlChatters.AllChatters.Staff.Count >= 0) lbViewer.Items.Add("");

                    if (ChannenlChatters.AllChatters.GlobalMods.Count >= 0) lbViewer.Items.Add("___________Global Mods__________");
                    foreach (string globalmods in ChannenlChatters.AllChatters.GlobalMods)
                    {
                        lbViewer.Items.Add(String.Format("{0,5}", globalmods));
                    }

                    if (ChannenlChatters.AllChatters.GlobalMods.Count >= 0) lbViewer.Items.Add("");
                    foreach (string globalMods in ChannenlChatters.AllChatters.GlobalMods)
                    {
                        lbViewer.Items.Add(String.Format("{0,5}", globalMods));
                    }

                    if (ChannenlChatters.AllChatters.Moderators.Count >= 0) lbViewer.Items.Add("___________Moderators___________");
                    foreach (string moderator in ChannenlChatters.AllChatters.Moderators)
                    {
                        lbViewer.Items.Add(String.Format("{0,5}", moderator));
                    }

                    if (ChannenlChatters.AllChatters.Viewers.Count >= 0) lbViewer.Items.Add("____________Viewers_____________");
                    foreach (string viewers in ChannenlChatters.AllChatters.Viewers)
                    {
                        lbViewer.Items.Add(String.Format("{0,5}", viewers));
                    }
                }
            }
        }
    }

所以我会阅读更多关于 ascny 的内容并等待并测试一下

【讨论】:

  • 您的计时器回调与您的点击处理程序所做的相同。它在 UI 线程上运行 everything。自然,这会在 UI 线程工作时阻塞它。如果没有可靠地重现问题的良好minimal reproducible example,就无法给出任何具体建议,但基本解决方案仍然相同:不要阻塞 UI 线程以进行长时间运行的操作。您似乎正在执行 I/O,因此您可能已经可以调用异步方法。如果没有,您可以使用 await Task.Run() 成语,就像我在此处发布的答案一样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多