【问题标题】:In multi threading environment, Application hanging while deleting large files在多线程环境中,删除大文件时应用程序挂起
【发布时间】:2020-06-07 12:10:42
【问题描述】:

在我的应用程序中,我正在删除大量文件,当我单击删除按钮时,我的应用程序 UI 将被挂起,直到删除完成。

这是导致挂起的方法。

  /// <summary>
  /// Attempts to acquire the CountDown, timing out after a specified
  /// interval.
  /// </summary>
  /// <param name="timeout">The maximum time to wait.</param>
  /// <returns>
  /// Boolean true if the CountDown was acquired.  Boolean false if the
  /// method timed out.
  /// </returns>
  public bool Attempt(TimeSpan timeout)
  {
     lock (this)
     {
        if (_count <= 0) return true;
        if (timeout <= TimeSpan.Zero) return false;

        TimeSpan waitTime = timeout;
        DateTime start = DateTime.Now;
        for (; ; )
        {
           // if the thread has been interrupted, Wait() will throw
           // ThreadInterruptedException()
           Monitor.Wait(this, waitTime);
           if (_count <= 0) return true;
           waitTime = timeout - (DateTime.Now - start);
           if (waitTime <= TimeSpan.Zero) return false;
        }
     }
  }

我执行的步骤:

  1. 单击了应用程序窗口窗体中的按钮“btnDel”。

  2. 然后应用程序进入挂起模式。

  3. 我点击了Break All(Ctrl+Alt+Break)。

当从 VS 按 Break All 时,它卡在 Monitor.Wait(this, waitTime); 并开始等待完成,我无法执行任何其他 UI 操作。

我正在阅读 MSDN 文章,其中有一些使用 Monitor 类管理线程的示例,但我认为我遗漏了一些东西。

有什么方法可以让 UI 摆脱这个后台删除操作?

当此删除在后台处于活动状态时,我想执行其他 UI 操作。

任何帮助将不胜感激,谢谢。

【问题讨论】:

  • 请分享minimal reproducible example。还要清楚这是 WPF 还是 Winforms。
  • 我建议将要删除的文件列表填充到 BlockingCollection 中,然后从新的 Task 处理该阻塞集合。
  • 我无法理解您问题的标题或正文如何以任何方式映射到您显示的代码。目前尚不清楚CountDown 是什么,以及为什么您觉得需要获取它。我也不清楚你想通过Wait 调用来实现什么。
  • 附带说明,如果调用 Monitor.Wait(this, waitTime) 发生并且 waitTime 是否定的,则调用可能会失败并返回 ArgumentOutOfRangeException。如果在应用程序执行期间系统时钟向后移动,则可能会发生这种情况。因此,不建议使用属性DateTime.Now 来测量间隔。 Stopwatch 类更适合此目的。

标签: c# multithreading monitor ui-thread background-thread


【解决方案1】:

我不得不进行一些推断,但您是否尝试执行类似this 的操作,其中 UI 显示剩余要删除的文件的运行计数?

如果这是一般的想法,那么对我有用的是使用 SemaphoreSlim 互斥对象(保持线程安全)和 CancellationToken 的组合。这样我们就不必在 UI 线程运行时阻塞它。

CancellationTokenSource _cts = null;
SemaphoreSlim ssBusy = new SemaphoreSlim(2);
private void DeleteManyFiles()
{
    try
    {
        ssBusy.Wait();
        switch (ssBusy.CurrentCount)
        {
            case 1:
                _cts = new CancellationTokenSource();
                for (int i = 0; i < NUMBER_OF_FILES_TO_DELETE; i++)
                {
                    bool cancelled = DeleteSingleFile(_cts.Token);
                    if(cancelled) break;
                }
                break;
            case 0:
                // A count of 0 indicates that the operation is already in progress.
                MessageBox.Show("Deletion is already in progress");
                break;
            default:
                break;
        }
    }
    catch(Exception ex)
    {
        Debug.Assert(false, ex.Message);
    }
    finally
    {
        ssBusy.Release();
    }
}

...哪里...

    private bool DeleteSingleFile(CancellationToken ct)
    {
        if(ct.IsCancellationRequested)
        {
            return true;
        }
        // Simulate half a second to delete one file
        Task.Delay(500).Wait();
        // SIMULATE ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

        // Decrement remaining count
        _remaining--;

        // Notify the UI thread
        FileDeleted?.Invoke(this, EventArgs.Empty);
        return false;
    }
    event EventHandler FileDeleted;

表单上的文本框会接收 FileDeleted 事件并为您提供倒计时。

private void TaskNotify_FileDeleted(object sender, EventArgs e)
{
    BeginInvoke((MethodInvoker)delegate 
    {
        textBoxRemaining.Text = _remaining.ToString(); 
    });
}

我的示例没有显示它,但是对于操作的超时 WDT,如果操作持续时间过长,请使用 CancellationToken 通过调用 _cts.Cancel() 来取消操作。

我在我们的 GitHub 上发布了完整的工作示例。如果对您有帮助,您可以浏览或Clone or Download

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-13
    • 1970-01-01
    • 2014-07-26
    • 1970-01-01
    • 1970-01-01
    • 2019-05-08
    • 2018-09-02
    相关资源
    最近更新 更多