【问题标题】:Multithreading with Semaphore | App not responding信号量的多线程 |应用程序没有响应
【发布时间】:2017-07-20 14:03:23
【问题描述】:

首先谈谈我的目标:

我正在将一个包含大约 1000-5000 行的表导入到 DataTable。这个绑定到DataGridView。现在对于每一行都必须运行一个大约需要 5-10 秒的过程。单个进程完成后,我想将结果写回DataTabel(结果列)。

因为这个过程是独立的,所以我想使用多线程来加速它。

这是我当前代码的示例结构:

// Will be created for each row
public class FooObject
{
    public int RowIndex;
    public string Name;
    //...
}

// Limiting running tasks to 50
private Semaphore semaphore = new Semaphore(50, 50);
// The DataTable is set up at start-up of the App (columns etc)
private DataTable DtData { get; set; } = new DataTable();

// The button that starts the process
private void btnStartLongRun(object sender, EventArgs e)
{
    // some init-stuff
    StartRun();
}

private async void StartRun()
{
    for (int rowIndex = 0; rowIndex < DtData.Rows.Count)
    {
        // Creating a task to not block the UI
        // Using semaphore here to not create objects
        // for all lines before they get in use.
        // Having this inside the real task it consumed
        // a lot of ram (> 1GB)
        await Task.Factory.StartNew(() => 
        {
            semaphore.WaitOne();
        });

        // The row to process
        var currentRow = DtData.Rows[rowIndex];

        // Creating an object from the row-data
        FooObject foo = new FooObject()
        {
            RowIndex = rowIndex;
            Name = currentRow["Name"].ToString();
        }

        // Not awaiting because I want multiple threads
        // to run at the same time. The semaphore is
        // handling this
        TaskScheduler scheduler = TaskScheduler.Current;
        Task.Factory.StartNew(() =>
        {
            // Per-row process
            return ProcessFoo(foo);
        }).ContinueWith((result) =>
        {
            FinishProcessFoo(result.Result);
        }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, scheduler);
    }
}

private FooObject ProcessFoo(FooObject foo)
{
    // the actual big process per line
}

private void FinishProcessFoo(FooObject foo)
{
    // Locking here because I got broken index errors without
    lock(DtGrid.Rows.SyncRoot)
    {
        // Getting the row that got processed
        var procRow = DtData.Rows[foo.RowIndex];
        // Writing the result to that row
        procRow["Result"] = foo.Result;

        // Raising the progressbar
        pbData.Value++;
    }

    // Letting the next task start.
    semaphore.Release();
}

最大的问题:

一开始一切正常。所有线程都运行顺利并完成他们的工作。但是,随着应用程序运行的时间越长,它越会变得无响应。看起来该应用正在慢慢开始越来越多地被阻止。

我开始了 5000 行的测试运行。它卡在第 2000 行左右。有时甚至会引发the app isn't responding 的错误。

我在多线程方面没有太多经验。所以也许这段代码完全不好。我感谢这里的每一个帮助。我也很乐意为我指出另一个方向以使其运行得更好。

非常感谢。

编辑
如果有什么我可以调试的,请告诉我。

编辑 2
我已经启用了所有 Common Language Runtime Exceptions 以检查是否有任何未引发错误的内容。什么都没有。

【问题讨论】:

  • 如果您想并行处理项目,为什么不简单地使用 Parallel.For?:docs.microsoft.com/en-us/dotnet/standard/parallel-programming/…
  • 因为我不能限制线程对吧?如果并行抛出所有行,应用程序将立即创建 5000 个对象,最终会消耗大量内存。也许我错了。但这就是我的想法。
  • 我不希望它们同时运行。必须有一个限度。 5000实在是太多了。我还需要写回结果。我想我需要并行调用控件。
  • 您可以使用 ParallelOptions.MaxDegreeOfParallelism 属性:stackoverflow.com/questions/9538452/…

标签: c# multithreading winforms task semaphore


【解决方案1】:

如果您想并行处理多达 50 行,您可以考虑使用 Parallel.For 和 50 的 MaxDegreeOfParallelism

Parallel.For(0, DtData.Rows.Count, new ParallelOptions() { MaxDegreeOfParallelism = 50 }, rowIndex => 
{
    //...
});

【讨论】:

  • 我同意@mm8,并行处理是这个的方法
  • 我需要一些时间来测试一下。我一定会回来提供反馈或标记为已回答。
  • 从parallel.for内部填充数据表时是否必须调用datagridview?
  • StartRun 内部,我用Parallel.For 替换了我的for-loop。用户界面完全冻结。
  • 我启动了一个通过Parallel.For 运行的线程。看起来崩溃已经消失了。我的问题没有清除“为什么”,但这是一个可行的选择。谢谢。
【解决方案2】:
  1. 开始一个新任务只是为了在信号量上调用 WaitOne 是浪费时间。

  2. 您正在使用 UI 线程来协调数千个异步任务。这是不好的。将您对 StartRun 的调用包含在一个新任务中以避免这种情况。

  3. 执行此操作的更好方法是将行数除以处理器数,然后每个处理器仅针对这些行启动一项任务。那么就不需要信号量了。

【讨论】:

  • 如果我将行拆分为我想要使用的线程数,它会一直等到所有 (5) 个线程完成,直到下一个 5 个开始。我宁愿尝试一次运行 5 次,但如果完成了一次,则开始另一个。我认为总是等待 5 开始新的 5 也是一种浪费。第2点对我来说很有意义。可能是崩溃的原因。我会试试这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-21
  • 1970-01-01
  • 2010-10-15
相关资源
最近更新 更多