Multithreading 有没有办法限制任务并行库使用的线程?
我正在使用TPL,但我发现使用它的单元测试代码很棘手 我尽量不去想它,因为我觉得它可能会带来一些问题 我知道您可以在TPL中设置处理器关联性,但最好设置线程最大值(可能是每个应用程序域)。因此,当将线程最大值设置为1时,TPL将被迫使用它所使用的任何线程 你觉得怎么样?这是可能的(我很确定不是),它应该是可能的吗 编辑:下面是一个示例Multithreading 有没有办法限制任务并行库使用的线程?,multithreading,unit-testing,concurrency,.net-4.0,task-parallel-library,Multithreading,Unit Testing,Concurrency,.net 4.0,Task Parallel Library,我正在使用TPL,但我发现使用它的单元测试代码很棘手 我尽量不去想它,因为我觉得它可能会带来一些问题 我知道您可以在TPL中设置处理器关联性,但最好设置线程最大值(可能是每个应用程序域)。因此,当将线程最大值设置为1时,TPL将被迫使用它所使用的任何线程 你觉得怎么样?这是可能的(我很确定不是),它应该是可能的吗 编辑:下面是一个示例 public class Foo { public Foo( ) { Task.Factory.StartNew( () =>
public class Foo
{
public Foo( )
{
Task.Factory.StartNew( () => somethingLong( ) )
.ContinueWith( a => Bar = 1 ) ;
}
}
[Test] public void Foo_should_set_Bar_to_1( )
{
Assert.Equal(1, new Foo( ).Bar ) ;
}
除非我推迟考试,否则考试可能不会通过。我希望有类似于
Task.MaximumThreads=1
的东西,以便TPL可以连续运行。您可以创建自己的TaskScheduler
类,该类派生自TaskScheduler
,并将其传递到TaskFactory
。现在,您可以让您创建的任何Task
对象针对该调度器运行
无需将其设置为使用一个线程
然后,在断言之前,只需对其调用Dispose()
。在内部,如果您按照前面的示例编写任务调度器
,它将执行类似的操作:-
public void Dispose()
{
if (tasks != null)
{
tasks.CompleteAdding();
foreach (var thread in threads) thread.Join();
tasks.Dispose();
tasks = null;
}
}
这将保证所有任务都已运行。现在你可以继续你的主张了
如果您想在事情发生时检查进度,您还可以使用
ContinueWith(…)
在任务运行后添加断言。实际上,与TPL相比,lambda重代码的可测试性更重要。Hightechrider的建议很好,但本质上,您的测试仍然在测试TPL,就像测试代码一样多。当第一个任务结束,下一个任务开始时,您实际上不需要进行测试
如果lambda中的代码非常大,那么将其提取到具有明确定义的参数的更易于测试的方法中可能会导致更易于阅读和更易于测试的代码。您可以围绕这一点编写单元测试。在可能的情况下,我尝试限制或删除单元测试中的并行性
话虽如此,我还是想看看调度器方法是否可行。下面是一个使用修改后的StaTaskScheduler的实现
使用系统;
使用System.Collections.Concurrent;
使用System.Collections.Generic;
使用System.Linq;
使用系统线程;
使用System.Threading.Tasks;
使用Xunit;
名称空间示例
{
公开课Foo
{
专用任务调度器_调度器;
公共整型条{get;set;}
私人空间
{
Thread.SpinWait(10000);
}
公共食物(
:此(TaskScheduler.Default)
{
}
公共Foo(任务调度程序)
{
_调度程序=调度程序;
}
公共工作
{
var factory=新任务工厂(_调度程序);
factory.StartNew(()=>SomethingLong())
.ContinueWith(a=>Bar=1,_调度程序);
}
}
公营足球场
{
[事实]
public void Foo_应将_Bar_设置为_1()
{
var sch=新的StaTaskScheduler(3);
var目标=新的Foo(sch);
target.DoWork();
sch.Dispose();
Assert.Equal(1,target.Bar);
}
}
公共密封类StaTaskScheduler:TaskScheduler,IDisposable
{
///存储要由STA线程池执行的排队任务。
私人封锁收集任务;
///调度程序使用的STA线程。
私有只读列表线程;
///使用指定的并发级别初始化StaTaskScheduler类的新实例。
///此计划程序应创建和使用的线程数。
公共任务调度程序(int numberOfThreads)
{
//验证参数
如果(numberOfThreads<1)抛出新的ArgumentOutOfRangeException(“concurrencyLevel”);
//初始化任务集合
_任务=新建BlockingCollection();
//创建此计划程序要使用的线程
_线程=可枚举。范围(0,numberOfThreads)。选择(i=>
{
变量线程=新线程(()=>
{
//不断地获取下一个任务并尝试执行它。
//此操作将继续,直到计划程序被释放且不再保留任何任务为止。
foreach(变量t在_tasks.GetConsumingEnumerable()中)
{
TryExecuteTask(t);
}
});
thread.IsBackground=true;
//不需要STA!
//SetApartmentState(ApartmentState.STA);
返回线程;
}).ToList();
//启动所有线程
_threads.ForEach(t=>t.Start());
}
///为此计划程序执行的任务排队。
///要执行的任务。
受保护的覆盖无效队列任务(任务任务)
{
//将其推到任务的阻塞集合中
_任务。添加(任务);
}
///提供调试器要使用的计划任务的列表。
///当前计划的所有任务的可枚举项。
受保护的重写IEnumerable GetScheduledTasks()
{
//序列化调试器任务的阻止集合的内容
return_tasks.ToArray();
}
///确定任务是否可以内联。
///要执行的任务。
///任务之前是否已排队。
///如果任务已成功内联,则为true;否则为false。
受保护的覆盖bool TryExecuteTaskInline(任务任务,bool任务先前排队)
{
//如果当前线程是STA,则尝试内联
返回
Thread.CurrentThread.GetApartmentState()==ApartmentState.STA&&
TryExecuteTask(任务);
}
///获取此计划程序支持的最大并发级别。
公共覆盖int最大并发级别
{
获取{return\u threads.Count;}
}
///
///通过指示不再执行任何任务来清理计划程序
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Example
{
public class Foo
{
private TaskScheduler _scheduler;
public int Bar { get; set; }
private void SomethingLong()
{
Thread.SpinWait(10000);
}
public Foo()
: this(TaskScheduler.Default)
{
}
public Foo(TaskScheduler scheduler)
{
_scheduler = scheduler;
}
public void DoWork()
{
var factory = new TaskFactory(_scheduler);
factory.StartNew(() => SomethingLong())
.ContinueWith(a => Bar = 1, _scheduler);
}
}
public class FooTests
{
[Fact]
public void Foo_should_set_Bar_to_1()
{
var sch = new StaTaskScheduler(3);
var target = new Foo(sch);
target.DoWork();
sch.Dispose();
Assert.Equal(1, target.Bar);
}
}
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
/// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>
private BlockingCollection<Task> _tasks;
/// <summary>The STA threads used by the scheduler.</summary>
private readonly List<Thread> _threads;
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
{
// Validate arguments
if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("concurrencyLevel");
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
{
var thread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
{
TryExecuteTask(t);
}
});
thread.IsBackground = true;
// NO STA REQUIREMENT!
// thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
// Start all of the threads
_threads.ForEach(t => t.Start());
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
// Push it into the blocking collection of tasks
_tasks.Add(task);
}
/// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
/// <returns>An enumerable of all tasks currently scheduled.</returns>
protected override IEnumerable<Task> GetScheduledTasks()
{
// Serialize the contents of the blocking collection of tasks for the debugger
return _tasks.ToArray();
}
/// <summary>Determines whether a Task may be inlined.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
/// <returns>true if the task was successfully inlined; otherwise, false.</returns>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Try to inline if the current thread is STA
return
Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&
TryExecuteTask(task);
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public override int MaximumConcurrencyLevel
{
get { return _threads.Count; }
}
/// <summary>
/// Cleans up the scheduler by indicating that no more tasks will be queued.
/// This method blocks until all threads successfully shutdown.
/// </summary>
public void Dispose()
{
if (_tasks != null)
{
// Indicate that no new tasks will be coming in
_tasks.CompleteAdding();
// Wait for all threads to finish processing tasks
foreach (var thread in _threads) thread.Join();
// Cleanup
_tasks.Dispose();
_tasks = null;
}
}
}
}
public class Foo
{
public Foo( )
{
Task.Factory.StartNew( () => somethingLong( ) )
.ContinueWith( a => Bar = 1 ) ;
}
}
[Test] public void Foo_should_set_Bar_to_1( )
{
Foo foo;
Task.Factory.ContinueWhenAll(
new [] {
new Task(() => {
foo = new Foo();
})
},
asserts => {
Assert.Equal(1, foo.Bar ) ;
}
).Wait;
}