【问题标题】:how to lock the application GUI in C# Winform [duplicate]如何在 C# Winform 中锁定应用程序 GUI [重复]
【发布时间】:2011-06-21 10:09:35
【问题描述】:

可能重复:
How to block Winforms UI while background thread is running

我正在使用 C# WinForm 应用程序

我在屏幕上有一个保存按钮,屏幕数据保存到数据库。

当用户点击按钮应用程序进入数据库并保存数据时会发生什么。 这需要一些时间。

但是,如果用户再次单击“保存”按钮,则会捕获 Click 事件,并且当第一次单击返回主代码(保存数据库后)时,会触发捕获的事件..

简而言之,当线程从第一个事件返回时,点击事件被捕获并触发 (我尝试了启用/禁用按钮的场景)。

我怎样才能阻止这种行为。

问候, 阿基尔

@Jalal:我尝试了这段代码并做了一些修改

private readonly object _userActivityLocker = new object();
        private void btnSave_Click(object sender, EventArgs e)
        {
            if (System.Threading.Monitor.TryEnter(_userActivityLocker))
            {
                //note that any sub clicks will be ignored while we are here
                try
                {
                    DateTime dt =  DateTime.Now;
                    Thread.Sleep(2000);
                    Debug.Print("FirstClick {0} Second Click {1}",dt.ToLongTimeString(),  DateTime.Now.ToLongTimeString());
                    //here it is safe to call the save and you can disable the btn
                    Application.DoEvents();
                }
                finally
                {
                    System.Threading.Monitor.Exit(_userActivityLocker);
                    //re-enable the btn if you disable it.
                }
            }
        }

但是当我快速点击按钮时(我检查了 5 次快速点击)5 个点击事件 已被触发并显示控制台窗口

第一次点击 1:30:22 PM 第二次点击 1:30:24 PM 第一次点击 1:30:24 PM 第二次点击 1:30:26 PM 第一次点击 1:30:26 PM 第二次点击 1:30:28 PM 第一次点击 1:30:28 PM 第二次点击 1:30:30 PM 第一次点击 1:30:30 PM 第二次点击 1:30:32 PM

【问题讨论】:

  • 奇怪……应该可以的
  • @V4Vendetta:这预计会发生..有关更多信息,请参阅我的答案。
  • 我忘记将代码包装在一个新线程中......我的错。但是我更新了答案。 ——

标签: c# winforms multithreading


【解决方案1】:

问题是您的程序在将数据保存到数据库时已经死了。用户的鼠标单击位于消息队列中,等待您的 UI 线程恢复活力。当它这样做时,按钮不再被禁用,因此 Click 事件触发。

您可以通过在重新启用按钮之前清空消息队列来解决此问题,以便在禁用按钮时处理单击:

    private void button1_Click(object sender, EventArgs e) {
        button1.Enabled = false;
        // Save data to database
        //...
        System.Threading.Thread.Sleep(2000);

        Application.DoEvents();   // Empty the message queue
        if (!button1.IsDisposed) button1.Enabled = true;
    }

不要跳过 IsDisposed 测试,DoEvents 很危险,因为它对处理哪些事件没有选择性。它会很高兴地让用户在您的代码仍在运行时关闭主窗口。

但更好的解决方案是不要让你的 UI 线程像这样死掉。使用 BackgroundWorker 在工作线程上执行保存。这也将避免 Windows 在您的保存时间超过几秒钟时出现的丑陋的“不响应”幽灵窗口。它现在可能还没有这样做,但是从现在起一年后 dbase 会增长。您可以在 BGW 的 RunWorkerCompleted 事件处理程序中重新启用该按钮。

【讨论】:

  • @Hans Passant:如果用户太快按下按钮两次,启用/禁用将无济于事,当服务器负载过大时可能会发生这种情况......
  • 嗯,你显然没有尝试过这段代码。
  • @Hans Passant:不!,当然我测试过,你现在可以自己测试一下,只需执行以下操作:创建一个新表单并将 button1 添加到它并在其单击事件处理程序上粘贴此代码button1.Enabled = false; Console.WriteLine("First Message"); Thread.Sleep(2000); Console.WriteLine("second Message"); button1.Enabled = true; 当然你必须双击按钮或快速单击两次才能看到该方法将被调用两次.....输出 将是 First Message second Message First Message second留言
  • 您忘记添加 DoEvents 调用,这是答案的关键。
  • @Hans +1 .. 不知道这个
【解决方案2】:

通过启用,然后再次启用,就像您逃避的那样。这有什么问题?

public void SaveButton_Click(..., ...)
{
    this.SaveButton.Enabled = false;
    Save();
    this.SaveButton.Enabled = true;
}

【讨论】:

  • 为了防止数据被保存两次,还可以添加一个布尔值[dataSaved],初始化为false。在保存数据之前,您将检查布尔变量:如果为 false,则保存,否则显示警告,说明数据已保存。更进一步,您还可以在用户更新数据集/数据表中的数据时将标志布尔变量设置为 false,以便用户能够再次保存修改后的数据。
  • @Josh Smeaton:我尝试了启用/禁用的东西。
  • @Moez:还有其他解决方案吗..?
  • @Akhil,这个解决方案有什么问题?你不是在帮助我们帮助你。
  • @Josh,你不明白吗?他已经试过了。
【解决方案3】:

使用System.Threading.Monitor 类可以达到以下目的:

private readonly object _userActivityLocker = new object();


private void btnSave_Click(object sender, EventArgs e)
{
    new Thread(delegate()
    {
    if (System.Threading.Monitor.TryEnter(_userActivityLocker))
    {
        //note that any sub clicks will be ignored while we are here
        try
        {
            //here it is safe to call the save and you can disable the btn
        }
        finally
        {
            System.Threading.Monitor.Exit(_userActivityLocker);
            //re-enable the btn if you disable it.
        }
    }
    }) { IsBackground = true }.Start();
}

为了证明将按钮更改为启用或禁用状态是不够的,这里做一个简单的测试:

添加一个新表单并为其添加一个 button1,然后在 button1 点击事件处理程序中编写以下代码:

private void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;

    Console.WriteLine("First Message");

    Thread.Sleep(2000);

    Console.WriteLine("second Message");

    button1.Enabled = true;
}

然后构建并运行应用程序,双击button1,输出窗口中的结果将是:

First Message
second Message
First Message
second Message

所以我们必须确保即使在双击左右时也只执行一次单击,并且只需使用System.Threading.Monitor即可完成

更新:请注意,您可以使用“如果 C# 4.0”任务、ThreadPool.QueueUserWorkItem 或 BackgroundWorker 来代替 Thread。

【讨论】:

  • 我修改了问题,请看一下
  • @kkhil:我忘记将代码包装在一个新线程中......我的错。但是我会更新我的答案。
  • @Akhil:你在编辑后尝试过这个吗?它满足您的需求吗?
  • @Hans Passant :创建线程是否比 Hans Passant 的答案更好......?我之前尝试过 Application.DoEvents() 来启用按钮,发现它工作正常。
  • @Akhil:在不同的线程上执行长操作比 Application.DoEvents() 好得多注意,“Hans Passant”也暗示了这一点,他说“But the better solution is to not let your UI thread go dead like this. Use a BackgroundWorker to perform the save on a worker threadBackgroundWorker目的是在单独的线程上执行操作。
猜你喜欢
  • 2014-09-16
  • 2014-02-18
  • 1970-01-01
  • 2011-07-10
  • 2021-05-20
  • 1970-01-01
  • 1970-01-01
  • 2010-10-21
相关资源
最近更新 更多