C# 运行X个任务数<;T>;在任何给定时间保持UI响应
我有一个使用TPL的C#WinForms(.NET 4.5.2)应用程序。该工具具有一个同步功能,它被传递到任务工厂C# 运行X个任务数<;T>;在任何给定时间保持UI响应,c#,winforms,async-await,task-parallel-library,C#,Winforms,Async Await,Task Parallel Library,我有一个使用TPL的C#WinForms(.NET 4.5.2)应用程序。该工具具有一个同步功能,它被传递到任务工厂X次数(使用不同的输入参数),其中X是用户在开始该过程之前声明的数字。任务将启动并存储在列表中 假设用户输入了5,我们在async按钮单击处理程序中有此项: for (int i = 0; i < X; i++) { var progress = Progress(); // returns a new IProgress<T> var task
X
次数(使用不同的输入参数),其中X
是用户在开始该过程之前声明的数字。任务将启动并存储在列表中
假设用户输入了5
,我们在async
按钮单击处理程序中有此项:
for (int i = 0; i < X; i++)
{
var progress = Progress(); // returns a new IProgress<T>
var task = Task<int>.Factory.StartNew(() => MyFunction(progress), TaskCreationOptions.LongRunning);
TaskList.Add(task);
}
这使UI陷入困境。有没有更好的方法来实现此功能,同时保持UI的响应性
评论中提出了使用TPL数据流的建议,但由于时间限制和规格,欢迎使用替代解决方案
更新
我不确定进度报告是否是问题所在?下面是它的样子:
private IProgress<string> Progress()
{
return new Progress<string>(msg =>
{
txtMsg.AppendText(msg);
});
}
private IProgress Progress()
{
返回新进度(msg=>
{
txtMsg.AppendText(msg);
});
}
现在,一项任务完成后,我想启动一项新任务。从本质上讲,流程应该无限期地运行,在任何给定的时间运行X个任务
在我看来,您希望在任务中有一个无限循环:
for (int i = 0; i < X; i++)
{
var progress = Progress(); // returns a new IProgress<T>
var task = RunIndefinitelyAsync(progress);
TaskList.Add(task);
}
private async Task RunIndefinitelyAsync(IProgress<T> progress)
{
while (true)
{
try
{
await Task.Run(() => MyFunction(progress));
// handle success
}
catch (Exception ex)
{
// handle exceptions
}
// update some labels/textboxes in the UI
}
}
for(int i=0;iMyFunction(progress));
//成功
}
捕获(例外情况除外)
{
//处理异常
}
//更新UI中的一些标签/文本框
}
}
但是,我怀疑“UI陷入困境”可能是在//处理成功
和/或//处理异常
代码中。如果我的怀疑是正确的,那么将尽可能多的逻辑放入任务。尽可能运行
现在,一项任务完成后,我想启动一项新任务。从本质上讲,流程应该无限期地运行,在任何给定的时间运行X个任务
在我看来,您希望在任务中有一个无限循环:
for (int i = 0; i < X; i++)
{
var progress = Progress(); // returns a new IProgress<T>
var task = RunIndefinitelyAsync(progress);
TaskList.Add(task);
}
private async Task RunIndefinitelyAsync(IProgress<T> progress)
{
while (true)
{
try
{
await Task.Run(() => MyFunction(progress));
// handle success
}
catch (Exception ex)
{
// handle exceptions
}
// update some labels/textboxes in the UI
}
}
for(int i=0;iMyFunction(progress));
//成功
}
捕获(例外情况除外)
{
//处理异常
}
//更新UI中的一些标签/文本框
}
}
但是,我怀疑“UI陷入困境”可能是在//处理成功
和/或//处理异常
代码中。如果我的怀疑是正确的,那么将尽可能多的逻辑放入任务中。尽可能运行。据我所知,您只需要定义并行化程度的并行执行。有很多方法可以实现你想要的。我建议使用阻塞集合和并行类代替任务
因此,当用户单击按钮时,您需要创建一个新的阻塞集合,该集合将成为您的数据源:
BlockingCollection<IProgress> queue = new BlockingCollection<IProgress>();
CancellationTokenSource source = new CancellationTokenSource();
或者您可以选择更正确的方式使用分区器。因此,您需要一个partitioner类:
private class BlockingPartitioner<T> : Partitioner<T>
{
private readonly BlockingCollection<T> _Collection;
private readonly CancellationToken _Token;
public BlockingPartitioner(BlockingCollection<T> collection, CancellationToken token)
{
_Collection = collection;
_Token = token;
}
public override IList<IEnumerator<T>> GetPartitions(int partitionCount)
{
throw new NotImplementedException();
}
public override IEnumerable<T> GetDynamicPartitions()
{
return _Collection.GetConsumingEnumerable(_Token);
}
public override bool SupportsDynamicPartitions
{
get { return true; }
}
}
私有类阻止分区器:分区器
{
私有只读阻止集合\u集合;
专用只读取消令牌\u令牌;
public BlockingPartitioner(BlockingCollection集合、CancellationToken令牌)
{
_收集=收集;
_令牌=令牌;
}
公共覆盖IList GetPartitions(int partitionCount)
{
抛出新的NotImplementedException();
}
公共重写IEnumerable GetDynamicPartitions()
{
return _Collection.getconsumineGenumerable(_令牌);
}
公共覆盖布尔支持动态分区
{
获取{return true;}
}
}
runner看起来像这样:
ParallelOptions Options = new ParallelOptions();
Options.MaxDegreeOfParallelism = X;
Task.Factory.StartNew(
() => Parallel.ForEach(
new BlockingPartitioner<IProgress>(queue, source.Token),
Options,
p => MyFunction(p)));
ParallelOptions=new ParallelOptions();
Options.MaxDegreeOfParallelism=X;
Task.Factory.StartNew(
()=>Parallel.ForEach(
新的BlockingPartitioner(队列,source.Token),
选项,
p=>MyFunction(p));
所以,您现在所需要的就是用必要的数据填充队列。你想干什么就干什么
最后一次触摸,当用户取消操作时,您有两个选项:
- 首先,您可以通过调用中断执行
- 或者,您可以通过标记collection complete()优雅地停止执行,在这种情况下,运行程序将执行所有已排队的数据并完成
当然,您需要额外的代码来处理异常、进度、状态等。但主要思想就在这里。据我所知,您只需要定义并行化程度的并行执行。有很多方法可以实现你想要的。我建议使用阻塞集合和并行类代替任务
因此,当用户单击按钮时,您需要创建一个新的阻塞集合,该集合将成为您的数据源:
BlockingCollection<IProgress> queue = new BlockingCollection<IProgress>();
CancellationTokenSource source = new CancellationTokenSource();
或者您可以选择更正确的方式使用分区器。因此,您需要一个partitioner类:
private class BlockingPartitioner<T> : Partitioner<T>
{
private readonly BlockingCollection<T> _Collection;
private readonly CancellationToken _Token;
public BlockingPartitioner(BlockingCollection<T> collection, CancellationToken token)
{
_Collection = collection;
_Token = token;
}
public override IList<IEnumerator<T>> GetPartitions(int partitionCount)
{
throw new NotImplementedException();
}
public override IEnumerable<T> GetDynamicPartitions()
{
return _Collection.GetConsumingEnumerable(_Token);
}
public override bool SupportsDynamicPartitions
{
get { return true; }
}
}
私有类阻止分区器:分区器
{
私有只读阻止集合\u集合;
专用只读取消令牌\u令牌;
public BlockingPartitioner(BlockingCollection集合、CancellationToken令牌)
{
_收集=收集;
_令牌=令牌;
}
公共覆盖IList GetPartitions(int partitionCount)
{
抛出新的NotImplementedException();
}
公共重写IEnumerable GetDynamicPartitions()
{
return _Collection.getconsumineGenumerable(_令牌);
}
公共覆盖布尔支持动态分区
{
获取{return true;}
}
}
runner看起来像这样:
ParallelOptions Options = new ParallelOptions();
Options.MaxDegreeOfParallelism = X;
Task.Factory.StartNew(
() => Parallel.ForEach(
new BlockingPartitioner<IProgress>(queue, source.Token),
Options,
p => MyFunction(p)));