使用信号量进行多线程处理|应用没响应

时间:2017-07-20 14:03:23

标签: c# multithreading winforms task semaphore

首先关于我的目标:

我正在将一个包含大约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以检查是否存在任何未引发错误的内容。什么都没有。

2 个答案:

答案 0 :(得分:1)

如果您想并行处理最多50行,可以考虑使用Parallel.For,其中MaxDegreeOfParallelism为50行:

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

答案 1 :(得分:0)

  1. 为了在信号量上调用WaitOne而开始一项新任务是浪费时间。

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

  3. 更好的方法是将行数除以处理器数,然后每个处理器为这些行启动一个任务。那时不需要信号量。