信号量的多线程 |应用程序没有响应
Multithreading with Semaphore | App not responding
先说说我的目标:
我正在将大约 1000-5000 行的 table 导入到 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();
}
大问题:
一开始一切正常。所有线程都 运行 正在顺利完成工作。但是应用程序 运行s 越长,它就会变得无响应。看起来应用程序正在慢慢开始阻止越来越多的人。
我开始了一个测试-运行 有 5000 行。它卡在第 2000 行左右。有时甚至会引发错误 the app isn't responding
。
我在多线程方面经验不多。所以也许这段代码是完全糟糕的。我感谢这里的每一个帮助。我也很乐意为我指明另一个方向,让这个 运行 变得更好。
非常感谢。
编辑
如果这里有任何我可以调试的帮助,请告诉我。
编辑 2
我已经启用所有 Common Language Runtime Exceptions
以检查是否有任何未引发错误的内容。没有。
如果您想并行处理多达 50 行,您可以考虑使用 Parallel.For
,MaxDegreeOfParallelism
为 50:
Parallel.For(0, DtData.Rows.Count, new ParallelOptions() { MaxDegreeOfParallelism = 50 }, rowIndex =>
{
//...
});
启动一个新任务只是为了在信号量上调用 WaitOne 是浪费时间。
您正在使用 UI 线程来协调数千个异步任务。这是不好的。在新任务中包装对 StartRun 的调用以避免这种情况。
更好的方法是将行数除以处理器数,然后仅针对这些行为每个处理器启动一个任务。那么就不需要信号量了。
先说说我的目标:
我正在将大约 1000-5000 行的 table 导入到 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();
}
大问题:
一开始一切正常。所有线程都 运行 正在顺利完成工作。但是应用程序 运行s 越长,它就会变得无响应。看起来应用程序正在慢慢开始阻止越来越多的人。
我开始了一个测试-运行 有 5000 行。它卡在第 2000 行左右。有时甚至会引发错误 the app isn't responding
。
我在多线程方面经验不多。所以也许这段代码是完全糟糕的。我感谢这里的每一个帮助。我也很乐意为我指明另一个方向,让这个 运行 变得更好。
非常感谢。
编辑
如果这里有任何我可以调试的帮助,请告诉我。
编辑 2
我已经启用所有 Common Language Runtime Exceptions
以检查是否有任何未引发错误的内容。没有。
如果您想并行处理多达 50 行,您可以考虑使用 Parallel.For
,MaxDegreeOfParallelism
为 50:
Parallel.For(0, DtData.Rows.Count, new ParallelOptions() { MaxDegreeOfParallelism = 50 }, rowIndex =>
{
//...
});
启动一个新任务只是为了在信号量上调用 WaitOne 是浪费时间。
您正在使用 UI 线程来协调数千个异步任务。这是不好的。在新任务中包装对 StartRun 的调用以避免这种情况。
更好的方法是将行数除以处理器数,然后仅针对这些行为每个处理器启动一个任务。那么就不需要信号量了。