2016-05-16 6 views
1

私は並列処理の開始を管理するスレッドモジュールをアプリケーションに持っています。それは、さまざまなタイミングとロギングの理由を追加します。そして、私は最近、私がバグにコード化して二重ネストされたスレッドのいくつかのタスクを開始していることを発見したほど複雑です。単位スレッドコードによって作成されたスレッド数のテスト

それは相当呼んでいた。すなわち

:一方では今

Task.Run(
    async() => await Task.Run(
     () => DoStuff(); 
    ); 
).Wait() 

を、そのコードは、ターゲットコードが実行されます...働き、およびターゲットコードがなるまで待機コードは続行されません。完了しました。

一方、1ではなく2つのスレッドを使用しています。スレッドスターベーションに問題があるため、問題があります。

私はコードを修正する方法を知っていますが、A)私はこのようなすべてのバグを修正しました/すべてのシナリオでそれを修正するために単体テストを書いたいと思います。 とB)誰もこのバグを将来再現しません。

しかし、私は "私が作成したすべてのスレッド"を取得する方法を見ることができません。 CurrentProcess.Threadsは私にスレッドの負荷を与え、私が気にしているスレッドを特定する明白な方法はありません。

どのような考えですか?

答えて

2

静的メソッド(この場合はTask.Run)が含まれているユニットをテストするソリューションでよくあることですが、これをラップする依存関係として何かを渡す必要があります。テストに。

@リーチは答えとして、TaskSchedulerを渡すことでこれを行うことができます。このテスト版では、エンキュー時にタスクの数を維持できます。

保護レベルのため、実際には少し醜いですが、この投稿の末尾には、既存のTaskScheduler(たとえば、TaskScheduler.Defaultを使用することができます)をラップするものが含まれています。

残念ながら、あなたもTaskScheduler.Defaultを除いて、basically what Task.Run does under the hoodある

Task.Factory.StartNew(
    () => DoSomething(), 
    CancellationToken.None, 
    TaskCreationOptions.DenyChildAttach, 
    myTaskScheduler); 

のようなものに

Task.Run(() => DoSomething); 

のようなあなたの呼び出しを変更する必要があります。あなたはもちろん、どこかでヘルパーメソッドでそれを包むことができます。

あなたがテストコードでいくつかのリスクの高い反射についてうるさいされていない場合、あなたはまだちょうどTask.Runを使用できるように別の方法として、あなたは、TaskScheduler.Defaultプロパティを乗っ取ることができます:

var defaultSchedulerField = typeof(TaskScheduler).GetField("s_defaultTaskScheduler", BindingFlags.Static | BindingFlags.NonPublic); 
var scheduler = new TestTaskScheduler(TaskScheduler.Default); 
defaultSchedulerField.SetValue(null, scheduler); 

(プライベートフィールド名がTaskScheduler.cs line 285からです。 )

したがって、たとえば、このテストは私のTestTaskScheduler以下と反射トリックを使用して渡します。

[Test] 
public void Can_count_tasks() 
{ 
    // Given 
    var originalScheduler = TaskScheduler.Default; 
    var defaultSchedulerField = typeof(TaskScheduler).GetField("s_defaultTaskScheduler", BindingFlags.Static | BindingFlags.NonPublic); 
    var testScheduler = new TestTaskScheduler(originalScheduler); 
    defaultSchedulerField.SetValue(null, testScheduler); 

    // When 
    Task.Run(() => {}); 
    Task.Run(() => {}); 
    Task.Run(() => {}); 

    // Then 
    testScheduler.TaskCount.Should().Be(3); 

    // Clean up 
    defaultSchedulerField.SetValue(null, originalScheduler); 
} 
using System.Collections.Generic; 
using System.Reflection; 
using System.Threading.Tasks; 

public class TestTaskScheduler : TaskScheduler 
{ 
    private static readonly MethodInfo queueTask = GetProtectedMethodInfo("QueueTask"); 
    private static readonly MethodInfo tryExecuteTaskInline = GetProtectedMethodInfo("TryExecuteTaskInline"); 
    private static readonly MethodInfo getScheduledTasks = GetProtectedMethodInfo("GetScheduledTasks"); 

    private readonly TaskScheduler taskScheduler; 

    public TestTaskScheduler(TaskScheduler taskScheduler) 
    { 
     this.taskScheduler = taskScheduler; 
    } 

    public int TaskCount { get; private set; } 

    protected override void QueueTask(Task task) 
    { 
     TaskCount++; 
     CallProtectedMethod(queueTask, task); 
    } 

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) 
    { 
     return (bool)CallProtectedMethod(tryExecuteTaskInline, task, taskWasPreviouslyQueued); 
    } 

    protected override IEnumerable<Task> GetScheduledTasks() 
    { 
     return (IEnumerable<Task>)CallProtectedMethod(getScheduledTasks); 
    } 

    private object CallProtectedMethod(MethodInfo methodInfo, params object[] args) 
    { 
     return methodInfo.Invoke(taskScheduler, args); 
    } 

    private static MethodInfo GetProtectedMethodInfo(string methodName) 
    { 
     return typeof(TaskScheduler).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic); 
    } 
} 

またはコメントで@hgcummingsにより示唆されるようRelflectionMagicを使用して片付け:ここ


は、テストタスクスケジューラで

var scheduler = new TestTaskScheduler(TaskScheduler.Default); 
typeof(TaskScheduler).AsDynamicType().s_defaultTaskScheduler = scheduler; 
using System.Collections.Generic; 
using System.Threading.Tasks; 
using ReflectionMagic; 

public class TestTaskScheduler : TaskScheduler 
{ 
    private readonly dynamic taskScheduler; 

    public TestTaskScheduler(TaskScheduler taskScheduler) 
    { 
     this.taskScheduler = taskScheduler.AsDynamic(); 
    } 

    public int TaskCount { get; private set; } 

    protected override void QueueTask(Task task) 
    { 
     TaskCount++; 
     taskScheduler.QueueTask(task); 
    } 

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) 
    { 
     return taskScheduler.TryExecuteTaskInline(task, taskWasPreviouslyQueued); 
    } 

    protected override IEnumerable<Task> GetScheduledTasks() 
    { 
     return taskScheduler.GetScheduledTasks(); 
    } 
} 
+0

それは...素晴らしい?恐ろしい?私は本当になぜこれをデフォルトのスケジューラとして使うのかはわかりませんが、あなたはJustDoできることではありませんが、どうやって今できるのか分かりました。 – Brondahl

+1

ニース(またはおそらく悪)! ReflectionMagicのAsDynamicを使用してTestTaskRunnerを少し簡略化することができます(ただし、パフォーマンスが低下する可能性があります)。 https://blogs.msdn.microsoft.com/davidebb/2010/01/18/use-c-4-0-dynamic-to-drastically-implify-your-private-reflection-code/ – hgcummings

+1

ニースを参照してください。私はいくつかのクリーンアップコードを追加して、テストの最後にTaskScheduler.Defaultを再び戻すか、または他の後のテストで望ましくない干渉を受けるかもしれません。 – Rich

2

Task.Runは、任意のスレッドを作成しません「私が作成したすべてのスレッド」のホールドを取得する方法。現在構成されているスレッドプール上でジョブを実行するようにスケジュールします。 https://msdn.microsoft.com/library/system.threading.tasks.taskscheduler.aspx

「私がエンキューしたタスクの数を数える方法」を意味する場合は、入力タスクを数え、それを使用するようにTaskSchedulerのカスタム実装を作成する必要があると思います。上にリンクされたページに表示されるカスタムTaskSchedulerの例があります。

+0

はい、あなたは正しいです。..それはまさに私が意味することです。私はあなたがそれをする方法を知っているとは思わない? /私はGoogleに新しいフレーズでオフになる – Brondahl

関連する問題