C# 运行X个任务数<;T>;在任何给定时间保持UI响应

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

我有一个使用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 = 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)));