├── CopyToAssets.bat
├── CopyToAssets.ps1
├── .gitignore
├── BasicSample
├── App.config
├── Program.cs
├── Common.cs
├── Properties
│ └── AssemblyInfo.cs
├── BasicSample.cs
├── ContinuationSample.cs
└── BasicSample.csproj
├── IteratorTasks
├── TaskScheduler.cs
├── IProgress.cs
├── TaskCanceledException.cs
├── TaskStatus.cs
├── Progress.cs
├── Util.cs
├── CancellationTokenSource.cs
├── Properties
│ └── AssemblyInfo.cs
├── CancellationToken.cs
├── MultiTask.cs
├── IteratorTasks.csproj
├── ContinuationTask.cs
└── Task.cs
├── AimingLib.vssscc
├── SampleTaskRunner
├── TaskRunner.cs
├── Properties
│ └── AssemblyInfo.cs
└── SampleTaskRunner.csproj
├── TestIteratorTasks
├── ProgressTest.cs
├── Properties
│ └── AssemblyInfo.cs
├── Coroutines.cs
├── CancellationTokenTest.cs
├── TestIteratorTasks.csproj
└── TaskTest.cs
├── README.md
├── LICENSE.md
└── AimingLib.sln
/CopyToAssets.bat:
--------------------------------------------------------------------------------
1 | PowerShell -Command "./CopyToAssets.ps1"
--------------------------------------------------------------------------------
/CopyToAssets.ps1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aiming/iterator-tasks/HEAD/CopyToAssets.ps1
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pidb
2 | *.suo
3 | *.userprefs
4 | *.vsmdi
5 | *.testsettings
6 | */bin
7 | */obj
8 | */publish
9 | $tf
10 | TestResults
11 | !*.sln
12 | !*.csproj
13 | !*/*.csproj
--------------------------------------------------------------------------------
/BasicSample/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/BasicSample/Program.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Sample
3 | {
4 | class Program
5 | {
6 | static void Main(string[] args)
7 | {
8 | ContinuationSample.Run();
9 | //BasicSample.Run();
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/IteratorTasks/TaskScheduler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Aiming.IteratorTasks
7 | {
8 | public abstract class TaskScheduler
9 | {
10 | protected internal abstract void QueueTask(Task task);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/AimingLib.vssscc:
--------------------------------------------------------------------------------
1 | ""
2 | {
3 | "FILE_VERSION" = "9237"
4 | "ENLISTMENT_CHOICE" = "NEVER"
5 | "PROJECT_FILE_RELATIVE_PATH" = ""
6 | "NUMBER_OF_EXCLUDED_FILES" = "0"
7 | "ORIGINAL_PROJECT_FILE_PATH" = ""
8 | "NUMBER_OF_NESTED_PROJECTS" = "0"
9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROJECT"
10 | }
11 |
--------------------------------------------------------------------------------
/IteratorTasks/IProgress.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Aiming.IteratorTasks
3 | {
4 | ///
5 | /// 進捗報告用のインターフェイス。
6 | ///
7 | /// 進捗度合を表す型。
8 | public interface IProgress
9 | {
10 | ///
11 | /// 現在の進捗状況をレポート。
12 | ///
13 | /// 現在の進捗状況。
14 | void Report(T value);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/IteratorTasks/TaskCanceledException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Aiming.IteratorTasks
4 | {
5 | public class TaskCanceledException : OperationCanceledException
6 | {
7 | public TaskCanceledException() { }
8 | public TaskCanceledException(string message) : base(message) { }
9 | public TaskCanceledException(string message, Exception innerException) : base(message, innerException) { }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/IteratorTasks/TaskStatus.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Aiming.IteratorTasks
3 | {
4 | public enum TaskStatus
5 | {
6 | ///
7 | /// 実行前。
8 | ///
9 | Created,
10 |
11 | ///
12 | /// 実行中。
13 | ///
14 | Running,
15 |
16 | ///
17 | /// 正常終了。
18 | ///
19 | RanToCompletion,
20 |
21 | ///
22 | /// キャンセルされた。
23 | ///
24 | Canceled,
25 |
26 | ///
27 | /// 例外が出て止まった。
28 | ///
29 | Faulted,
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/BasicSample/Common.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Aiming.IteratorTasks;
3 |
4 | namespace Sample
5 | {
6 | class Common
7 | {
8 | public static Task ShowFrameTask(int numFrames)
9 | {
10 | return new Task(() => ShowFrame(numFrames));
11 | }
12 |
13 | ///
14 | /// 毎フレーム、500ミリ秒スリープして、フレーム数を表示する。
15 | ///
16 | ///
17 | public static System.Collections.IEnumerator ShowFrame(int numFrames)
18 | {
19 | for (int i = 0; i < numFrames; i++)
20 | {
21 | System.Threading.Thread.Sleep(500);
22 | Console.WriteLine("frame: {0}", i);
23 | yield return null;
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/IteratorTasks/Progress.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Aiming.IteratorTasks
4 | {
5 | ///
6 | /// イベントを使って進捗報告を受け取るためのクラス。
7 | ///
8 | /// 進捗度合を表す型。
9 | public class Progress : IProgress
10 | {
11 | public Progress() { }
12 |
13 | public Progress(Action onProgressChanged) { ProgressChanged += onProgressChanged; }
14 |
15 | ///
16 | /// 進捗状況が変化したときに起こすイベント。
17 | ///
18 | public event Action ProgressChanged;
19 |
20 | void IProgress.Report(T value)
21 | {
22 | var d = ProgressChanged;
23 | if (d != null) d(value);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/IteratorTasks/Util.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 |
4 | namespace Aiming.IteratorTasks
5 | {
6 | public static class Util
7 | {
8 | ///
9 | /// Unity の StartCoroutine を呼ぶ際に、null を渡しても平気かどうかわからなかったので、ダミーの 0 要素の IEnumeartor を返す。
10 | /// 要検証かも。
11 | ///
12 | ///
13 | public static IEnumerator EmptyRoutine()
14 | {
15 | return new object[0].GetEnumerator();
16 | }
17 |
18 | public static IEnumerator Concat(Func e1, Func e2)
19 | {
20 | var x1 = e1();
21 | while (x1.MoveNext()) yield return x1.Current;
22 | Dispose(x1);
23 |
24 | var x2 = e2();
25 | while (x2.MoveNext()) yield return x2.Current;
26 | Dispose(x2);
27 | }
28 |
29 | public static void Dispose(object obj)
30 | {
31 | var d = obj as IDisposable;
32 | if (d != null) d.Dispose();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/SampleTaskRunner/TaskRunner.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Aiming.IteratorTasks;
3 |
4 | namespace SampleTaskRunner
5 | {
6 | public class TaskRunner : TaskScheduler
7 | {
8 | private List _runningTasks = new List();
9 | private List _toBeRemoved = new List();
10 |
11 | protected override void QueueTask(Task task)
12 | {
13 | _runningTasks.Add(task);
14 | }
15 |
16 | ///
17 | /// 1フレーム処理を進める。
18 | ///
19 | public void Update()
20 | {
21 | foreach (var t in _runningTasks)
22 | {
23 | if (!t.MoveNext())
24 | _toBeRemoved.Add(t);
25 | }
26 |
27 | foreach (var t in _toBeRemoved)
28 | {
29 | _runningTasks.Remove(t);
30 | }
31 | _toBeRemoved.Clear();
32 | }
33 |
34 | ///
35 | /// 与えたフレーム数分、処理を進める。
36 | ///
37 | /// 進めたいフレーム数。
38 | public void Update(int numFrames)
39 | {
40 | for (int i = 0; i < numFrames; i++)
41 | {
42 | this.Update();
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/IteratorTasks/CancellationTokenSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Aiming.IteratorTasks
4 | {
5 | ///
6 | /// タスクのキャンセル用トークン。
7 | /// キャンセルする側。
8 | ///
9 | public class CancellationTokenSource
10 | {
11 | public CancellationTokenSource()
12 | {
13 | Token = new CancellationToken(this);
14 | }
15 |
16 | ///
17 | /// キャンセル用トークン。
18 | ///
19 | public CancellationToken Token { get; private set; }
20 |
21 | ///
22 | /// キャンセル要求を出したかどうか。
23 | ///
24 | public bool IsCancellationRequested { get; private set; }
25 |
26 | ///
27 | /// キャンセル。
28 | ///
29 | public void Cancel()
30 | {
31 | var d = Canceled;
32 | if (d != null) d();
33 |
34 | IsCancellationRequested = true;
35 | }
36 |
37 | ///
38 | /// キャンセルの原因となる例外を指定してのキャンセル要求。
39 | ///
40 | public void Cancel(Exception cancelReason)
41 | {
42 | _cancelReason = cancelReason;
43 |
44 | this.Cancel();
45 | }
46 |
47 | internal event Action Canceled;
48 |
49 | private Exception _cancelReason;
50 | internal Exception CancelReason { get { return _cancelReason; } }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/TestIteratorTasks/ProgressTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 | using Aiming.IteratorTasks;
7 |
8 | namespace TestIteratorTasks
9 | {
10 | [TestClass]
11 | public class ProgressTest
12 | {
13 | [TestMethod]
14 | public void Progressでは_Reportが呼ばれるたびにProgressChangedイベントが起こる()
15 | {
16 | var progress = new Progress();
17 | var reportedItems = new List();
18 |
19 | progress.ProgressChanged += i =>
20 | {
21 | reportedItems.Add(i);
22 | };
23 |
24 | var t = new Task(c => 進捗報告付きのコルーチン(c, progress));
25 |
26 | var runner = new SampleTaskRunner.TaskRunner();
27 | t.Start(runner);
28 |
29 | for (int i = 0; i < 100; i++)
30 | {
31 | Assert.AreEqual(i, reportedItems.Count);
32 | runner.Update();
33 | Assert.AreEqual(i + 1, reportedItems.Count);
34 | Assert.AreEqual(i, reportedItems.Last());
35 | }
36 | }
37 |
38 | static System.Collections.IEnumerator 進捗報告付きのコルーチン(Action completed, IProgress progress)
39 | {
40 | for (int i = 0; i < 100; i++)
41 | {
42 | progress.Report(i);
43 | yield return null;
44 | }
45 |
46 | completed(100);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/BasicSample/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
6 | // アセンブリに関連付けられている情報を変更するには、
7 | // これらの属性値を変更してください。
8 | [assembly: AssemblyTitle("BasicSample")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("BasicSample")]
13 | [assembly: AssemblyCopyright("Copyright © 2012")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから
18 | // 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、
19 | // その型の ComVisible 属性を true に設定してください。
20 | [assembly: ComVisible(false)]
21 |
22 | // 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です
23 | [assembly: Guid("9afc6961-1dbb-43d6-b762-1f617622ea2f")]
24 |
25 | // アセンブリのバージョン情報は、以下の 4 つの値で構成されています:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を
33 | // 既定値にすることができます:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/SampleTaskRunner/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
6 | // アセンブリに関連付けられている情報を変更するには、
7 | // これらの属性値を変更してください。
8 | [assembly: AssemblyTitle("SampleTaskRunner")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SampleTaskRunner")]
13 | [assembly: AssemblyCopyright("Copyright © 2012")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから
18 | // 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、
19 | // その型の ComVisible 属性を true に設定してください。
20 | [assembly: ComVisible(false)]
21 |
22 | // 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です
23 | [assembly: Guid("22390451-5763-472c-aad7-0461392ed9c6")]
24 |
25 | // アセンブリのバージョン情報は、以下の 4 つの値で構成されています:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を
33 | // 既定値にすることができます:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/TestIteratorTasks/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
6 | // アセンブリに関連付けられている情報を変更するには、
7 | // これらの属性値を変更してください。
8 | [assembly: AssemblyTitle("TestIteratorTasks")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("TestIteratorTasks")]
13 | [assembly: AssemblyCopyright("Copyright © 2012")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから
18 | // 参照できなくなります。このアセンブリ内で COM から型にアクセスする必要がある場合は、
19 | // その型の ComVisible 属性を true に設定してください。
20 | [assembly: ComVisible(false)]
21 |
22 | // このプロジェクトが COM に公開される場合、次の GUID がタイプ ライブラリの ID になります。
23 | [assembly: Guid("f0c69c3f-db5e-4317-9963-3ea28ab28783")]
24 |
25 | // アセンブリのバージョン情報は、以下の 4 つの値で構成されています:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // すべての値を指定するか、以下のように '*' を使用してビルド番号とリビジョン番号を
33 | // 既定値にすることができます:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/IteratorTasks/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
6 | // アセンブリに関連付けられている情報を変更するには、
7 | // これらの属性値を変更してください。
8 | [assembly: AssemblyTitle("Aiming.IteratorTasks")]
9 | [assembly: AssemblyDescription("Task Asynchrony Library for Iterator-based Coroutine")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Aiming")]
12 | [assembly: AssemblyProduct("IteratorTasks")]
13 | [assembly: AssemblyCopyright("Copyright © Aiming 2012")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから
18 | // 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、
19 | // その型の ComVisible 属性を true に設定してください。
20 | [assembly: ComVisible(false)]
21 |
22 | // 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です
23 | [assembly: Guid("dd7396f2-3e4d-4ab2-9de8-4b9f9257538c")]
24 |
25 | // アセンブリのバージョン情報は、以下の 4 つの値で構成されています:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を
33 | // 既定値にすることができます:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/IteratorTasks/CancellationToken.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Aiming.IteratorTasks
4 | {
5 | ///
6 | /// タスクのキャンセル用トークン。
7 | /// キャンセルを受ける側。
8 | ///
9 | public struct CancellationToken
10 | {
11 | private CancellationTokenSource _source;
12 |
13 | internal CancellationToken(CancellationTokenSource source) { _source = source; }
14 |
15 | ///
16 | /// キャンセル要求が出ているかどうか。
17 | ///
18 | public bool IsCancellationRequested
19 | {
20 | get
21 | {
22 | if (_source == null)
23 | return false;
24 | else
25 | return _source.IsCancellationRequested;
26 | }
27 | }
28 |
29 | ///
30 | /// キャンセル要求時に通知を受け取るためのデリゲートを登録。
31 | ///
32 | /// キャンセル要求時に呼ばれるデリゲート。
33 | public void Register(Action onCanceled)
34 | {
35 | if (_source != null)
36 | _source.Canceled += onCanceled;
37 | }
38 |
39 | ///
40 | /// 空のトークン。
41 | ///
42 | public static CancellationToken None = new CancellationToken();
43 |
44 | ///
45 | /// キャンセル要求が出ている場合、OperationCanceledException をスローする。
46 | ///
47 | public void ThrowIfCancellationRequested()
48 | {
49 | if (IsCancellationRequested)
50 | {
51 | var e = _source.CancelReason != null ?
52 | _source.CancelReason :
53 | new TaskCanceledException();
54 | throw e;
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/BasicSample/BasicSample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Aiming.IteratorTasks;
3 |
4 | namespace Sample
5 | {
6 | ///
7 | /// 3つのタスクを同時に動かす例。
8 | ///
9 | class BasicSample
10 | {
11 | public static void Run()
12 | {
13 | var runner = new SampleTaskRunner.TaskRunner();
14 |
15 | Common.ShowFrameTask(50).Start(runner);
16 |
17 | new Task(Worker1)
18 | .OnComplete(t => Console.WriteLine("Worker 1 Done: " + t.Result))
19 | .Start(runner);
20 |
21 | new Task(Worker2)
22 | .OnComplete(t => Console.WriteLine("Worker 2 Done: " + t.Result))
23 | .Start(runner);
24 |
25 | runner.Update(200);
26 | }
27 |
28 | ///
29 | /// 30フレーム掛けて何かやった体で、文字列を返すコルーチン。
30 | ///
31 | /// 1フレームに処理が集中しないように分割して実行するイメージ。
32 | ///
33 | ///
34 | ///
35 | private static System.Collections.IEnumerator Worker1(Action completed)
36 | {
37 | Console.WriteLine("Start Worker 1");
38 |
39 | for (int i = 0; i < 30; i++)
40 | {
41 | yield return null;
42 | }
43 |
44 | completed("Result");
45 | }
46 |
47 | ///
48 | /// 3秒掛けて何かやった体で、数値を返すコルーチン。
49 | ///
50 | /// スレッドを立ててスリープしている部分を、時間がかかる計算や、ネットワーク待ちに置き換えて考えていただけると。
51 | ///
52 | ///
53 | ///
54 | private static System.Collections.IEnumerator Worker2(Action completed)
55 | {
56 | bool done = false;
57 | int result = 0;
58 |
59 | System.Threading.ThreadPool.QueueUserWorkItem(state =>
60 | {
61 | System.Threading.Thread.Sleep(3000);
62 | result = 999;
63 | done = true;
64 | });
65 |
66 | Console.WriteLine("Start Worker 2");
67 |
68 | while (!done)
69 | yield return null;
70 |
71 | completed(result);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/IteratorTasks/MultiTask.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace Aiming.IteratorTasks
7 | {
8 | ///
9 | /// 複数の例外を束ねる例外クラス。
10 | /// 並行動作してると、複数のタスク内で同時に例外が発生する可能性があるので。
11 | ///
12 | public class AggregateException : Exception
13 | {
14 | private List _exceptions;
15 | public IEnumerable Exceptions { get { return _exceptions.ToArray(); } }
16 |
17 | public AggregateException(List exceptions)
18 | {
19 | _exceptions = exceptions;
20 | }
21 |
22 | public override string Message
23 | {
24 | get
25 | {
26 | var count = _exceptions.Count;
27 |
28 | if (count == 1) return _exceptions[0].Message;
29 | else if (count > 1) return string.Format("AggregateException: {0} errors", count);
30 | else return base.Message;
31 | }
32 | }
33 | }
34 |
35 | // もともと、Util とか MultiTask みたいな名前の静的クラスに持たせようかと思っていたものの、
36 | // 結局、Task クラス自身に並行動作用のメソッドを持たせることに。
37 | public partial class Task
38 | {
39 | public static Task WhenAllTask(params Task[] routines)
40 | {
41 | return new Task(WhenAll(routines));
42 | }
43 |
44 | ///
45 | /// 複数の Task を並行実行する。
46 | ///
47 | ///
48 | /// 1フレームにつき、1つの Task の MoveNext しか呼ばないので、
49 | /// N 個の Task を並行実行すると、個々の MoveNext は N フレームに1回しか呼ばれない。
50 | ///
51 | /// 同時に動かしたい Task。
52 | /// 束ねたコルーチン。
53 | public static IEnumerator WhenAll(params Task[] routines)
54 | {
55 | int successCount = 0;
56 | var errors = new List();
57 |
58 | foreach (var r in routines)
59 | {
60 | r.OnComplete(t =>
61 | {
62 | if (t.Error == null) ++successCount;
63 | else errors.AddRange(t.Error.Exceptions);
64 | });
65 | }
66 |
67 | do
68 | {
69 | for (int i = 0; i < routines.Length; i++)
70 | {
71 | var r = routines[i];
72 | if (r == null) continue;
73 |
74 | if (r.MoveNext())
75 | {
76 | yield return r.Current;
77 | }
78 | else
79 | routines[i] = null;
80 | }
81 | } while (routines.Any(x => x != null));
82 |
83 | if (errors.Count != 0) throw new AggregateException(errors);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/BasicSample/ContinuationSample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Aiming.IteratorTasks;
3 |
4 | namespace Sample
5 | {
6 | ///
7 | /// 継続処理のサンプル。
8 | ///
9 | /// 同期処理とイテレーター非同期処理の対応関係を説明。
10 | /// U F(T x) → IEnumerator FAsync(T x, Action<U<);
11 | /// F2(F1(x)) → new Task<U<(c => F1Async(x, c).ContinueWith<V<(F2Async);
12 | ///
13 | class ContinuationSample
14 | {
15 | public static void Run()
16 | {
17 | var x = 1.41421356;
18 |
19 | // 同期処理
20 | var result = F3(F2(F1(x)));
21 | Console.WriteLine("同期処理の結果: " + result);
22 |
23 | // イテレーター非同期処理
24 | var task = new Task(c => F1Async(x, c))
25 | .ContinueWith(F2Async)
26 | .ContinueWith(F3Async)
27 | .OnComplete(t => Console.WriteLine("非同期処理の結果: " + t.Result));
28 |
29 | var runner = new SampleTaskRunner.TaskRunner();
30 |
31 | task.Start(runner);
32 | Common.ShowFrameTask(50).Start(runner);
33 |
34 | runner.Update(20);
35 | }
36 |
37 | #region 同期処理
38 |
39 | // 中身には深い意味なし。
40 | // 単に、F3(F2(F1(x))) みたいに繋ぎたいだけ。
41 |
42 | private static double F1(double x)
43 | {
44 | return x * x;
45 | }
46 |
47 | private static string F2(double x)
48 | {
49 | return x.ToString();
50 | }
51 |
52 | private static int F3(string s)
53 | {
54 | return s.Length;
55 | }
56 |
57 | #endregion
58 | #region イテレーター非同期処理
59 |
60 | ///
61 | /// 5フレーム後にF1の結果を返す。
62 | ///
63 | private static System.Collections.IEnumerator F1Async(double x, Action completed)
64 | {
65 | for (int i = 0; i < 5; i++)
66 | {
67 | yield return null;
68 | }
69 |
70 | Console.WriteLine("F1Async 終了");
71 | var result = F1(x);
72 | completed(result);
73 | }
74 |
75 | ///
76 | /// 5フレーム後にF2の結果を返す。
77 | ///
78 | private static System.Collections.IEnumerator F2Async(double x, Action completed)
79 | {
80 | for (int i = 0; i < 5; i++)
81 | {
82 | yield return null;
83 | }
84 |
85 | Console.WriteLine("F2Async 終了");
86 | var result = F2(x);
87 | completed(result);
88 | }
89 |
90 | ///
91 | /// 5フレーム後にF3の結果を返す。
92 | ///
93 | private static System.Collections.IEnumerator F3Async(string s, Action completed)
94 | {
95 | for (int i = 0; i < 5; i++)
96 | {
97 | yield return null;
98 | }
99 |
100 | Console.WriteLine("F3Async 終了");
101 | var result = F3(s);
102 | completed(result);
103 | }
104 |
105 | #endregion
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Iterator Tasks
2 |
3 | Iterator Tasks is a iterator-based coroutine class library.
4 |
5 | ## Asynchronous Operations on Unity
6 |
7 | One of the purpose of Iterator Tasks is to simplify asynchronous operations on Unity 3D game engine. Unity is built on Mono, open source .NET development framework. It however uses older version which correspond approximately to .NET Framework 3.5 and C# 3.0. Thus, there is no Task class and async/await support.
8 |
9 | Alternatively, Unity provides iterator-based coroutine framework for asynchronous operation. For example, following code awaits web access completoin without blocking a thread by using a yield return statement.
10 |
11 | ```c#
12 | using UnityEngine;
13 | using System.Collections;
14 |
15 | public class example : MonoBehaviour
16 | {
17 | public string url = "http://images.earthcam.com/ec_metros/ourcams/fridays.jpg";
18 | IEnumerator Start()
19 | {
20 | WWW www = new WWW(url);
21 | yield return www;
22 | renderer.material.mainTexture = www.texture;
23 | }
24 | }
25 | ```
26 |
27 | However, there are not so many developers understanding usage of iterator block (yield return statement). Thus, it is slightly difficult to use iterator block for asynchronous operation, especially when a return value of the asynchronous operation is needed for subsequent tasks.
28 |
29 | Because of this, Iterator Tasks wraps a iterator-based coroutine with a class which resembles Task class in .NET 4.
30 |
31 | ## Example Usage
32 |
33 | Here is an example of Task class in Iterator Tasks.
34 |
35 | ```c#
36 | var task = new Task(c => Coroutines.F1Async(x, c))
37 | .ContinueWith(Coroutines.F2Async)
38 | .ContinueWith(Coroutines.F3Async);
39 |
40 | task.OnComplete(t => Console.WriteLine(t.Result));
41 | ```
42 |
43 | Where F1Async, F2Async, F3Async are iterator-based coroutine as follows:
44 |
45 | ```c#
46 | public static System.Collections.IEnumerator F1Async(double x, Action completed)
47 | {
48 | for (int i = 0; i < 5; i++) yield return null;
49 | var result = F1(x);
50 | completed(result);
51 | }
52 |
53 | public static System.Collections.IEnumerator F2Async(double x, Action completed)
54 | {
55 | for (int i = 0; i < 5; i++) yield return null;
56 | var result = F2(x);
57 | completed(result);
58 | }
59 |
60 | public static IEnumerator F3Async(string s, Action completed)
61 | {
62 | for (int i = 0; i < 5; i++) yield return null;
63 | var result = F3(s);
64 | completed(result);
65 | }
66 | ```
67 |
68 | # [License](https://github.com/aiming/iterator-tasks/blob/master/LICENSE.md)
69 |
70 | # Contributing
71 | 1. Fork it
72 | 2. Create your feature branch (`git checkout -b my-new-feature`)
73 | 3. Commit your changes (`git commit -am 'Add some feature'`)
74 | 4. Push to the branch (`git push origin my-new-feature`)
75 | 5. Create new Pull Request
76 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | --------
2 |
3 | Copyright (C) 2012 Aiming Inc.
4 |
5 | --------
6 |
7 | The license of this library is based on the following license.
8 |
9 | --------
10 |
11 | # Microsoft Public License (Ms-PL)
12 |
13 | Microsoft Public License (Ms-PL)
14 |
15 | This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
16 |
17 | ## 1. Definitions
18 |
19 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
20 |
21 | A "contribution" is the original software, or any additions or changes to the software.
22 |
23 | A "contributor" is any person that distributes its contribution under this license.
24 |
25 | "Licensed patents" are a contributor's patent claims that read directly on its contribution.
26 |
27 | ## 2. Grant of Rights
28 |
29 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
30 |
31 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
32 |
33 | ## 3. Conditions and Limitations
34 |
35 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
36 |
37 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
38 |
39 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
40 |
41 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
42 |
43 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
44 |
--------------------------------------------------------------------------------
/SampleTaskRunner/SampleTaskRunner.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {5BF3A7F6-634B-4B1E-B3B2-134ECF8B1B26}
8 | Library
9 | Properties
10 | SampleTaskRunner
11 | SampleTaskRunner
12 | v3.5
13 | 512
14 |
15 | SAK
16 | SAK
17 | SAK
18 | SAK
19 |
20 |
21 | true
22 | full
23 | false
24 | bin\Debug\
25 | DEBUG;TRACE
26 | prompt
27 | 4
28 |
29 |
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {3ab99f84-7e6f-4b01-9f57-ae5722c56893}
52 | IteratorTasks
53 |
54 |
55 |
56 |
63 |
--------------------------------------------------------------------------------
/IteratorTasks/IteratorTasks.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {3AB99F84-7E6F-4B01-9F57-AE5722C56893}
8 | Library
9 | Properties
10 | IteratorTasks
11 | IteratorTasks
12 | v3.5
13 | 512
14 |
15 | SAK
16 | SAK
17 | SAK
18 | SAK
19 |
20 |
21 | true
22 | full
23 | false
24 | bin\Debug\
25 | DEBUG;TRACE
26 | prompt
27 | 4
28 |
29 |
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
67 |
--------------------------------------------------------------------------------
/AimingLib.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IteratorTasks", "IteratorTasks\IteratorTasks.csproj", "{3AB99F84-7E6F-4B01-9F57-AE5722C56893}"
5 | EndProject
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleTaskRunner", "SampleTaskRunner\SampleTaskRunner.csproj", "{5BF3A7F6-634B-4B1E-B3B2-134ECF8B1B26}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicSample", "BasicSample\BasicSample.csproj", "{B2D257A2-953D-434E-8056-C32E5F2D7489}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestIteratorTasks", "TestIteratorTasks\TestIteratorTasks.csproj", "{A3FBC7EE-46C0-4A22-BD44-FDFCE425672D}"
11 | EndProject
12 | Global
13 | GlobalSection(TeamFoundationVersionControl) = preSolution
14 | SccNumberOfProjects = 5
15 | SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
16 | SccTeamFoundationServer = https://tfs.codeplex.com/tfs/tfs37
17 | SccLocalPath0 = .
18 | SccProjectUniqueName1 = BasicSample\\BasicSample.csproj
19 | SccProjectName1 = BasicSample
20 | SccLocalPath1 = BasicSample
21 | SccProjectUniqueName2 = IteratorTasks\\IteratorTasks.csproj
22 | SccProjectName2 = IteratorTasks
23 | SccLocalPath2 = IteratorTasks
24 | SccProjectUniqueName3 = SampleTaskRunner\\SampleTaskRunner.csproj
25 | SccProjectName3 = SampleTaskRunner
26 | SccLocalPath3 = SampleTaskRunner
27 | SccProjectUniqueName4 = TestIteratorTasks\\TestIteratorTasks.csproj
28 | SccProjectName4 = TestIteratorTasks
29 | SccLocalPath4 = TestIteratorTasks
30 | EndGlobalSection
31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
32 | Debug|Any CPU = Debug|Any CPU
33 | Release|Any CPU = Release|Any CPU
34 | EndGlobalSection
35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
36 | {3AB99F84-7E6F-4B01-9F57-AE5722C56893}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {3AB99F84-7E6F-4B01-9F57-AE5722C56893}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {3AB99F84-7E6F-4B01-9F57-AE5722C56893}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {3AB99F84-7E6F-4B01-9F57-AE5722C56893}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {5BF3A7F6-634B-4B1E-B3B2-134ECF8B1B26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {5BF3A7F6-634B-4B1E-B3B2-134ECF8B1B26}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {5BF3A7F6-634B-4B1E-B3B2-134ECF8B1B26}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {5BF3A7F6-634B-4B1E-B3B2-134ECF8B1B26}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {B2D257A2-953D-434E-8056-C32E5F2D7489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {B2D257A2-953D-434E-8056-C32E5F2D7489}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {B2D257A2-953D-434E-8056-C32E5F2D7489}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {B2D257A2-953D-434E-8056-C32E5F2D7489}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {A3FBC7EE-46C0-4A22-BD44-FDFCE425672D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {A3FBC7EE-46C0-4A22-BD44-FDFCE425672D}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {A3FBC7EE-46C0-4A22-BD44-FDFCE425672D}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {A3FBC7EE-46C0-4A22-BD44-FDFCE425672D}.Release|Any CPU.Build.0 = Release|Any CPU
52 | EndGlobalSection
53 | GlobalSection(SolutionProperties) = preSolution
54 | HideSolutionNode = FALSE
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/BasicSample/BasicSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {B2D257A2-953D-434E-8056-C32E5F2D7489}
8 | Exe
9 | Properties
10 | BasicSample
11 | BasicSample
12 | v3.5
13 | 512
14 |
15 | SAK
16 | SAK
17 | SAK
18 | SAK
19 |
20 |
21 | AnyCPU
22 | true
23 | full
24 | false
25 | bin\Debug\
26 | DEBUG;TRACE
27 | prompt
28 | 4
29 |
30 |
31 | AnyCPU
32 | pdbonly
33 | true
34 | bin\Release\
35 | TRACE
36 | prompt
37 | 4
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | {3ab99f84-7e6f-4b01-9f57-ae5722c56893}
60 | IteratorTasks
61 |
62 |
63 | {5bf3a7f6-634b-4b1e-b3b2-134ecf8b1b26}
64 | SampleTaskRunner
65 |
66 |
67 |
68 |
75 |
--------------------------------------------------------------------------------
/TestIteratorTasks/Coroutines.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Aiming.IteratorTasks;
6 |
7 | namespace TestIteratorTasks
8 | {
9 | ///
10 | /// テスト用に使うコルーチンいろいろ。
11 | ///
12 | class Coroutines
13 | {
14 | #region 同期処理
15 |
16 | // 中身には深い意味なし。
17 | // 単に、F3(F2(F1(x))) みたいに繋ぎたいだけ。
18 |
19 | public static double F1(double x)
20 | {
21 | return x * x;
22 | }
23 |
24 | public static string F2(double x)
25 | {
26 | return x.ToString();
27 | }
28 |
29 | public static int F3(string s)
30 | {
31 | return s.Length;
32 | }
33 |
34 | public static int FError()
35 | {
36 | throw new NotSupportedException();
37 | }
38 |
39 | #endregion
40 | #region イテレーター非同期処理
41 |
42 | public static Task NFrameTask(int n)
43 | {
44 | return new Task(() => NFrame(n));
45 | }
46 |
47 | public static System.Collections.IEnumerator NFrame(int n)
48 | {
49 | for (int i = 0; i < n; i++)
50 | {
51 | yield return null;
52 | }
53 | }
54 |
55 | ///
56 | /// 5フレーム後にF1の結果を返す。
57 | ///
58 | public static System.Collections.IEnumerator F1Async(double x, Action completed)
59 | {
60 | for (int i = 0; i < 5; i++)
61 | {
62 | yield return null;
63 | }
64 |
65 | var result = F1(x);
66 | completed(result);
67 | }
68 |
69 | ///
70 | /// 5フレーム後にF2の結果を返す。
71 | ///
72 | public static System.Collections.IEnumerator F2Async(double x, Action completed)
73 | {
74 | for (int i = 0; i < 5; i++)
75 | {
76 | yield return null;
77 | }
78 |
79 | var result = F2(x);
80 | completed(result);
81 | }
82 |
83 | ///
84 | /// 5フレーム後にF3の結果を返す。
85 | ///
86 | public static System.Collections.IEnumerator F3Async(string s, Action completed)
87 | {
88 | for (int i = 0; i < 5; i++)
89 | {
90 | yield return null;
91 | }
92 |
93 | var result = F3(s);
94 | completed(result);
95 | }
96 |
97 | ///
98 | /// 5フレーム後にNotSupportedExceptionを出す。
99 | ///
100 | public static System.Collections.IEnumerator FErrorAsync()
101 | {
102 | for (int i = 0; i < 5; i++)
103 | {
104 | yield return null;
105 | }
106 |
107 | FError();
108 | }
109 |
110 | ///
111 | /// 5フレーム後にNotSupportedExceptionを出す。
112 | ///
113 | public static System.Collections.IEnumerator FErrorAsync(Action campleted)
114 | {
115 | for (int i = 0; i < 5; i++)
116 | {
117 | yield return null;
118 | }
119 |
120 | var result = FError();
121 | campleted(result);
122 | }
123 |
124 | #endregion
125 | #region キャンセル機能付き
126 |
127 | ///
128 | /// n フレーム後に F1(x)を返すコルーチン。
129 | /// キャンセル機能付き。
130 | ///
131 | /// 入力値。
132 | /// コルーチン稼働のフレーム数。
133 | /// 完了時に呼ばれるデリゲート。
134 | /// キャンセル用トークン。
135 | ///
136 | public static System.Collections.IEnumerator F1Cancelable(double x, int n, Action completed, CancellationToken ct)
137 | {
138 | for (int i = 0; i < n; i++)
139 | {
140 | // キャンセルへの対処はあくまでコルーチン側の債務
141 | // 例外を出して止める。
142 | ct.ThrowIfCancellationRequested();
143 |
144 | yield return null;
145 | }
146 |
147 | completed(F1(x));
148 | }
149 |
150 | #endregion
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/TestIteratorTasks/CancellationTokenTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 | using Aiming.IteratorTasks;
7 |
8 | namespace TestIteratorTasks
9 | {
10 | [TestClass]
11 | public class CancellationTokenTest
12 | {
13 | [TestMethod]
14 | public void キャンセルトークンを渡しても_Cancelを呼ばなければ正常終了()
15 | {
16 | var x = 10;
17 | var runner = new SampleTaskRunner.TaskRunner();
18 |
19 | var t = new Task(c => Coroutines.F1Cancelable(x, 20, c, CancellationToken.None));
20 |
21 | t.Start(runner);
22 | while (!t.IsCompleted)
23 | runner.Update();
24 |
25 | Assert.AreEqual(Coroutines.F1(x), t.Result);
26 | }
27 |
28 | [TestMethod]
29 | public void キャンセルしたときにOperationCanceld例外発生()
30 | {
31 | var x = 10;
32 | var runner = new SampleTaskRunner.TaskRunner();
33 |
34 | var cts = new CancellationTokenSource();
35 | var t = new Task(c => Coroutines.F1Cancelable(x, 20, c, cts.Token));
36 |
37 | t.Start(runner);
38 | runner.Update(5);
39 | cts.Cancel();
40 |
41 | // 次の1回の実行でタスクが終わるはず
42 | runner.Update();
43 |
44 | // この場合は IsCanceled にならない
45 | Assert.IsTrue(t.IsFaulted);
46 | Assert.AreEqual(typeof(TaskCanceledException), t.Error.Exceptions.Single().GetType());
47 | }
48 |
49 | [TestMethod]
50 | public void TaskのForceCancelで強制的にタスクを止めたときはOnCompleteも呼ばれない()
51 | {
52 | var x = 10;
53 | var runner = new SampleTaskRunner.TaskRunner();
54 |
55 | var cts = new CancellationTokenSource();
56 | var t = new Task(c => Coroutines.F1Cancelable(x, 20, c, cts.Token));
57 |
58 | t.OnComplete(_ =>
59 | {
60 | Assert.Fail();
61 | });
62 |
63 | t.Start(runner);
64 | runner.Update(5);
65 | t.ForceCancel();
66 |
67 | runner.Update();
68 |
69 | // この場合は IsCanceled に
70 | Assert.IsTrue(t.IsCanceled);
71 | }
72 |
73 | [TestMethod]
74 | public void TaskにCancellationTokenSourceを渡しておいて_TaskのCancelメソッド経由でキャンセルできる()
75 | {
76 | var x = 10;
77 | var runner = new SampleTaskRunner.TaskRunner();
78 |
79 | var cts = new CancellationTokenSource();
80 | var t = new Task(c => Coroutines.F1Cancelable(x, 20, c, cts.Token));
81 |
82 | t.Cancellation = cts;
83 |
84 | t.Start(runner);
85 | runner.Update(5);
86 | t.Cancel(); // Task.Cancel の中で1度 MoveNext して、即座にキャンセル処理が動くようにする
87 |
88 | // 挙動自体は cts.Cancel(); と同じ
89 | Assert.IsTrue(t.IsFaulted);
90 | Assert.AreEqual(typeof(TaskCanceledException), t.Error.Exceptions.Single().GetType());
91 | }
92 |
93 | [TestMethod]
94 | public void Cancel時にRegisterで登録したデリゲートが呼ばれる()
95 | {
96 | var runner = new SampleTaskRunner.TaskRunner();
97 |
98 | {
99 | // キャンセルしない場合
100 | var cts = new CancellationTokenSource();
101 | var t = new Task(c => Cancelで戻り値が切り替わるコルーチン(10, c, cts.Token));
102 | t.Start(runner);
103 | runner.Update(20);
104 |
105 | Assert.IsTrue(t.IsCompleted);
106 | Assert.AreEqual(CompletedMessage, t.Result);
107 | }
108 |
109 | {
110 | // キャンセルする場合
111 | var cts = new CancellationTokenSource();
112 | var t = new Task(c => Cancelで戻り値が切り替わるコルーチン(10, c, cts.Token));
113 | t.Start(runner);
114 | runner.Update(5);
115 | cts.Cancel();
116 | runner.Update(5);
117 |
118 | Assert.IsTrue(t.IsCompleted);
119 | Assert.AreEqual(CanceledMessage, t.Result);
120 | }
121 | }
122 |
123 | const string CompletedMessage = "最後まで実行された時の戻り値";
124 | const string CanceledMessage = "キャンセルされた時の戻り値";
125 |
126 | static System.Collections.IEnumerator Cancelで戻り値が切り替わるコルーチン(int n, Action completed, CancellationToken ct)
127 | {
128 | var message = CompletedMessage;
129 |
130 | ct.Register(() =>
131 | {
132 | message = CanceledMessage;
133 | });
134 |
135 | for (int i = 0; i < n; i++)
136 | {
137 | if (ct.IsCancellationRequested)
138 | break;
139 |
140 | yield return null;
141 | }
142 |
143 | completed(message);
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/TestIteratorTasks/TestIteratorTasks.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {A3FBC7EE-46C0-4A22-BD44-FDFCE425672D}
7 | Library
8 | Properties
9 | TestIteratorTasks
10 | TestIteratorTasks
11 | v3.5
12 | 512
13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 10.0
15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
17 | False
18 | UnitTest
19 |
20 | SAK
21 | SAK
22 | SAK
23 | SAK
24 |
25 |
26 | true
27 | full
28 | false
29 | bin\Debug\
30 | DEBUG;TRACE
31 | prompt
32 | 4
33 |
34 |
35 | pdbonly
36 | true
37 | bin\Release\
38 | TRACE
39 | prompt
40 | 4
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | {3ab99f84-7e6f-4b01-9f57-ae5722c56893}
67 | IteratorTasks
68 |
69 |
70 | {5bf3a7f6-634b-4b1e-b3b2-134ecf8b1b26}
71 | SampleTaskRunner
72 |
73 |
74 |
75 |
76 |
77 |
78 | False
79 |
80 |
81 | False
82 |
83 |
84 | False
85 |
86 |
87 | False
88 |
89 |
90 |
91 |
92 |
93 |
94 |
101 |
--------------------------------------------------------------------------------
/IteratorTasks/ContinuationTask.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 |
4 | namespace Aiming.IteratorTasks
5 | {
6 | public partial class Task
7 | {
8 | ///
9 | /// 1つのタスク完了後に、続けて処理をする新しいタスクを生成。
10 | ///
11 | /// 継続処理のタスク生成メソッド。
12 | /// 新しいタスク。
13 | public Task ContinueWithTask(Func continuation)
14 | {
15 | return new ContinuationTask(this, continuation);
16 | }
17 |
18 | ///
19 | /// 1つのタスク完了後に、続けて処理をする新しいタスクを生成。
20 | ///
21 | /// 継続処理のコルーチン生成メソッド。
22 | /// 新しいタスク。
23 | public Task ContinueWith(Func continuation)
24 | {
25 | return this.ContinueWithTask(() => new Task(continuation()));
26 | }
27 |
28 | ///
29 | /// 1つのタスク完了後に、続けて処理をする新しいタスクを生成。
30 | ///
31 | /// 継続タスクの戻り値の型。
32 | /// 継続処理のタスク生成メソッド。
33 | /// 新しいタスク。
34 | public Task ContinueWithTask(Func> continuation)
35 | {
36 | return new ContinuationTask(this, continuation);
37 | }
38 |
39 | ///
40 | /// 1つのタスク完了後に、続けて処理をする新しいタスクを生成。
41 | ///
42 | /// 継続タスクの戻り値の型。
43 | /// 継続処理のコルーチン生成メソッド。
44 | /// 新しいタスク。
45 | public Task ContinueWith(Func, IEnumerator> continuation)
46 | {
47 | return this.ContinueWithTask(() => new Task(continuation));
48 | }
49 | }
50 |
51 | public partial class Task
52 | {
53 | ///
54 | /// 1つのタスク完了後に、続けて処理をする新しいタスクを生成。
55 | ///
56 | /// 継続処理のタスク生成メソッド。
57 | /// 新しいタスク。
58 | public Task ContinueWithTask(Func continuation)
59 | {
60 | return new ContinuationTask(this, () =>
61 | {
62 | var res = this.Result;
63 | this._result = default(T);
64 | GC.Collect();
65 | return continuation(res);
66 | });
67 | }
68 |
69 | ///
70 | /// 1つのタスク完了後に、続けて処理をする新しいタスクを生成。
71 | ///
72 | /// 継続処理のコルーチン生成メソッド。
73 | /// 新しいタスク。
74 | public Task ContinueWith(Func continuation)
75 | {
76 | return this.ContinueWithTask(() => new Task(continuation(this.Result)));
77 | }
78 |
79 | ///
80 | /// 1つのタスク完了後に、続けて処理をする新しいタスクを生成。
81 | ///
82 | /// 継続タスクの戻り値の型。
83 | /// 継続処理のタスク生成メソッド。
84 | /// 新しいタスク。
85 | public Task ContinueWithTask(Func> continuation)
86 | {
87 | return new ContinuationTask(this, () =>
88 | {
89 | var res = this.Result;
90 | return continuation(res);
91 | });
92 | }
93 |
94 | ///
95 | /// 1つのタスク完了後に、続けて処理をする新しいタスクを生成。
96 | ///
97 | /// 継続タスクの戻り値の型。
98 | /// 継続処理のコルーチン生成メソッド。
99 | /// 新しいタスク。
100 | public Task ContinueWith(Func, IEnumerator> continuation)
101 | {
102 | Func> f = x => new Task(a => continuation(x, a));
103 | return this.ContinueWithTask(f);
104 | }
105 | }
106 |
107 | ///
108 | /// ContinuationTask のジェネリック版と非ジェネリック版でほとんど同じ処理なのに、継承とかで処理を使いまわせないのでやむなく別クラスに。
109 | ///
110 | internal class ContinuationTaskInternal
111 | {
112 | private Task _task;
113 | private Func _continuation;
114 | private Action _addError;
115 | private Action _complete;
116 | internal Task LastTask { get; private set; }
117 |
118 | internal ContinuationTaskInternal(Task firstTask, Func continuation, Action addError, Action complete)
119 | {
120 | _task = firstTask;
121 | _continuation = continuation;
122 | _addError = addError;
123 | _complete = complete;
124 | LastTask = null;
125 | }
126 |
127 | public object Current
128 | {
129 | get
130 | {
131 | if (_task != null)
132 | return _task.Current;
133 | return null;
134 | }
135 | }
136 |
137 | public bool MoveNext()
138 | {
139 | if (_task != null)
140 | {
141 | var hasNext = _task.MoveNext();
142 |
143 | if (hasNext) return true;
144 |
145 | if (_task.Error != null)
146 | {
147 | _addError(_task.Error);
148 | Complete();
149 | return false;
150 | }
151 |
152 | if (_continuation != null)
153 | {
154 | _task = _continuation();
155 | _continuation = null;
156 | return true;
157 | }
158 | else
159 | {
160 | Complete();
161 | return false;
162 | }
163 | }
164 |
165 | return false;
166 | }
167 |
168 | private void Complete()
169 | {
170 | LastTask = _task;
171 | _complete();
172 | _task = null;
173 | _continuation = null;
174 | }
175 | }
176 |
177 | ///
178 | /// 継続処理用のタスク クラス。
179 | ///
180 | internal class ContinuationTask : Task
181 | {
182 | ContinuationTaskInternal _inner;
183 |
184 | internal ContinuationTask(Task firstTask, Func continuation)
185 | {
186 | _inner = new ContinuationTaskInternal(firstTask, continuation, this.AddError, this.Complete);
187 | }
188 |
189 | public override object Current { get { return _inner.Current; } }
190 |
191 | public override bool MoveNext()
192 | {
193 | return _inner.MoveNext();
194 | }
195 | }
196 |
197 | ///
198 | /// 継続処理用のタスク クラス。
199 | /// ジェネリック版(戻り値あり)。
200 | ///
201 | internal class ContinuationTask : Task
202 | {
203 | ContinuationTaskInternal _inner;
204 |
205 | internal ContinuationTask(Task firstTask, Func> continuation)
206 | {
207 | _inner = new ContinuationTaskInternal(firstTask, () => continuation(), this.AddError, this.Complete);
208 | }
209 |
210 | public override object Current { get { return _inner.Current; } }
211 |
212 | public override bool MoveNext()
213 | {
214 | return _inner.MoveNext();
215 | }
216 |
217 | public override U Result
218 | {
219 | get
220 | {
221 | var t = _inner.LastTask as Task;
222 | if (t != null)
223 | {
224 | return t.Result;
225 | }
226 | else
227 | {
228 | return default(U);
229 | }
230 | }
231 | }
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/TestIteratorTasks/TaskTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 | using Aiming.IteratorTasks;
7 |
8 | namespace TestIteratorTasks
9 | {
10 | [TestClass]
11 | public class TaskTest
12 | {
13 |
14 | [TestMethod]
15 | public void Nフレーム実行するイテレーターが丁度NフレームIsCompleted_falseになってることを確認()
16 | {
17 | const int N = 50;
18 |
19 | var runnner = new SampleTaskRunner.TaskRunner();
20 | var task = Coroutines.NFrameTask(N);
21 | task.Start(runnner);
22 |
23 | for (int i = 0; i < 2 * N; i++)
24 | {
25 | runnner.Update();
26 |
27 | if (i < N)
28 | Assert.IsFalse(task.IsCompleted);
29 | else
30 | Assert.IsTrue(task.IsCompleted);
31 | }
32 | }
33 |
34 | [TestMethod]
35 | public void Task_Tで正常終了するとResultに結果が入る()
36 | {
37 | var x = 10;
38 | var y = Coroutines.F1(x);
39 |
40 | var task = new Task(c => Coroutines.F1Async(x, c))
41 | .OnComplete(t => Assert.AreEqual(t.Result, y));
42 |
43 | var runnner = new SampleTaskRunner.TaskRunner();
44 | task.Start(runnner);
45 | runnner.Update(10);
46 |
47 | Assert.AreEqual(y, task.Result);
48 | }
49 |
50 | [TestMethod]
51 | public void 一度完了したタスク_何度でも結果が取れる()
52 | {
53 | var x = 10;
54 | var y = Coroutines.F1(x);
55 |
56 | var task = new Task(c => Coroutines.F1Async(x, c));
57 |
58 | var runnner = new SampleTaskRunner.TaskRunner();
59 | task.Start(runnner);
60 | runnner.Update(10);
61 |
62 | Assert.AreEqual(y, task.Result);
63 | Assert.AreEqual(y, task.Result);
64 | Assert.AreEqual(y, task.Result);
65 | Assert.AreEqual(y, task.Result);
66 | }
67 |
68 | [TestMethod]
69 | public void タスク完了時にOnCompleteが呼ばれる()
70 | {
71 | var runnner = new SampleTaskRunner.TaskRunner();
72 |
73 | var x = 10;
74 | var y = Coroutines.F1(x);
75 |
76 | bool called = false;
77 |
78 | var task = new Task(c => Coroutines.F1Async(x, c));
79 | task.OnComplete(t => called = true);
80 | task.Start(runnner);
81 |
82 | Assert.IsFalse(called);
83 |
84 | runnner.Update(10);
85 |
86 | Assert.IsTrue(called);
87 | }
88 |
89 | [TestMethod]
90 | public void 完了済みのタスクでOnCompleteすると_即座にコールバックが呼ばれる()
91 | {
92 | var runnner = new SampleTaskRunner.TaskRunner();
93 |
94 | var x = 10;
95 | var y = Coroutines.F1(x);
96 |
97 | var task = new Task(c => Coroutines.F1Async(x, c));
98 | task.Start(runnner);
99 | runnner.Update(10);
100 |
101 | Assert.IsTrue(task.IsCompleted);
102 |
103 | bool called = false;
104 |
105 | task.OnComplete(t => called = true);
106 |
107 | Assert.IsTrue(called);
108 | }
109 |
110 | [TestMethod]
111 | public void 開始前_実行中_正常終了_エラー終了_キャンセルされた_がわかる()
112 | {
113 | var x = 10.0;
114 | var task = new Task(c => Coroutines.F1Async(x, c));
115 |
116 | Assert.AreEqual(TaskStatus.Created, task.Status);
117 |
118 | var runnner = new SampleTaskRunner.TaskRunner();
119 | task.Start(runnner);
120 |
121 | runnner.Update();
122 | Assert.AreEqual(TaskStatus.Running, task.Status);
123 |
124 | runnner.Update(10);
125 | Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
126 |
127 | var errorTask = new Task(Coroutines.FErrorAsync);
128 | Assert.AreEqual(TaskStatus.Created, errorTask.Status);
129 |
130 | errorTask.Start(runnner);
131 | runnner.Update();
132 | Assert.AreEqual(TaskStatus.Running, errorTask.Status);
133 |
134 | runnner.Update(10);
135 | Assert.AreEqual(TaskStatus.Faulted, errorTask.Status);
136 | }
137 |
138 | [TestMethod]
139 | public void 実行途中のタスクを再スタートしようとしたら例外を出す()
140 | {
141 | var x = 10.0;
142 | var task = new Task(c => Coroutines.F1Async(x, c));
143 |
144 | Assert.AreEqual(TaskStatus.Created, task.Status);
145 |
146 | var runnner = new SampleTaskRunner.TaskRunner();
147 | task.Start(runnner);
148 |
149 | runnner.Update();
150 | Assert.AreEqual(TaskStatus.Running, task.Status);
151 |
152 | try
153 | {
154 | task.Start(runnner);
155 | }
156 | catch (InvalidOperationException)
157 | {
158 | return;
159 | }
160 |
161 | Assert.Fail();
162 | }
163 |
164 | [TestMethod]
165 | public void タスク中で例外が出たらErrorプロパティに例外が入る()
166 | {
167 | var task = new Task(Coroutines.FErrorAsync)
168 | .OnComplete(t =>
169 | {
170 | Assert.IsTrue(t.IsFaulted);
171 | Assert.IsNotNull(t.Error);
172 | })
173 | .ContinueWith(呼ばれてはいけない);
174 |
175 | var runner = new SampleTaskRunner.TaskRunner();
176 | task.Start(runner);
177 | runner.Update(20);
178 | }
179 |
180 | [TestMethod]
181 | public void タスク中で例外が出たらOnErrorが呼ばれる_特定の型の例外だけ拾う()
182 | {
183 | var notSupportedCalled = false;
184 | var outOfRangeCalled = false;
185 |
186 | var task = new Task(Coroutines.FErrorAsync)
187 | .OnError(e => notSupportedCalled = true)
188 | .OnError(e => outOfRangeCalled = true);
189 |
190 | var runner = new SampleTaskRunner.TaskRunner();
191 | task.Start(runner);
192 | runner.Update(20);
193 |
194 | Assert.IsTrue(notSupportedCalled);
195 | Assert.IsFalse(outOfRangeCalled);
196 | }
197 |
198 | [TestMethod]
199 | public void タスク中で例外が出たときにResultをとろうとすると例外再スロー()
200 | {
201 | var task = new Task(Coroutines.FErrorAsync)
202 | .OnComplete(t =>
203 | {
204 | Assert.IsTrue(t.IsFaulted);
205 |
206 | try
207 | {
208 | var result = t.Result;
209 | }
210 | catch
211 | {
212 | return;
213 | }
214 | Assert.Fail();
215 | })
216 | .ContinueWith(呼ばれてはいけない);
217 |
218 | var runner = new SampleTaskRunner.TaskRunner();
219 | task.Start(runner);
220 | runner.Update(20);
221 | }
222 |
223 | [TestMethod]
224 | public void ContinueWithで継続処理を実行できる()
225 | {
226 | var x = 10.0;
227 | var x1 = Coroutines.F1(x);
228 | var x2 = Coroutines.F2(x1);
229 | var x3 = Coroutines.F3(x2);
230 |
231 | var task = new Task(c => Coroutines.F1Async(x, c))
232 | .OnComplete(t => Assert.AreEqual(t.Result, x1))
233 | .ContinueWith(Coroutines.F2Async)
234 | .OnComplete(t => Assert.AreEqual(t.Result, x2))
235 | .ContinueWith(Coroutines.F3Async)
236 | .OnComplete(t => Assert.AreEqual(t.Result, x2))
237 | ;
238 |
239 | var runner = new SampleTaskRunner.TaskRunner();
240 | task.Start(runner);
241 |
242 | runner.Update(20);
243 | }
244 |
245 | [TestMethod]
246 | public void ContinueWithは前段が正常終了したときにだけ呼ばれる()
247 | {
248 | var onCompletedCalled = false;
249 |
250 | var task = new Task(Coroutines.FErrorAsync)
251 | .OnComplete(t =>
252 | {
253 | Assert.IsTrue(t.IsFaulted);
254 | onCompletedCalled = true;
255 | })
256 | .ContinueWith(呼ばれてはいけない);
257 |
258 | var runner = new SampleTaskRunner.TaskRunner();
259 | task.Start(runner);
260 | runner.Update(20);
261 |
262 | Assert.IsTrue(onCompletedCalled);
263 | }
264 |
265 | private System.Collections.IEnumerator 呼ばれてはいけない()
266 | {
267 | Assert.Fail();
268 | yield return null;
269 | }
270 |
271 | [TestMethod]
272 | public void OnCompleteは_直前のタスク完了時_エラーも正常終了も_どちらも呼ばれる()
273 | {
274 | var errorTaskCalled = false;
275 | var normalTaskCalled = false;
276 |
277 | var normalTask = new Task(() => Coroutines.NFrame(5))
278 | .OnComplete(t => normalTaskCalled = true);
279 | var errorTask = new Task(Coroutines.FErrorAsync)
280 | .OnComplete(t => errorTaskCalled = true);
281 |
282 | var runner = new SampleTaskRunner.TaskRunner();
283 | errorTask.Start(runner);
284 | normalTask.Start(runner);
285 | runner.Update(20);
286 |
287 | Assert.IsTrue(normalTaskCalled);
288 | Assert.IsTrue(errorTaskCalled);
289 | }
290 |
291 | [TestMethod]
292 | public void WhenAllでタスクの並行動作できる()
293 | {
294 | var t1 = new Task(() => Coroutines.NFrame(3));
295 | var t2 = new Task(() => Coroutines.NFrame(5));
296 | var t3 = new Task(() => Coroutines.NFrame(7));
297 |
298 | var task = Task.WhenAllTask(t1, t2, t3)
299 | .OnComplete(t =>
300 | {
301 | Assert.IsTrue(t1.IsCompleted);
302 | Assert.IsTrue(t2.IsCompleted);
303 | Assert.IsTrue(t3.IsCompleted);
304 | });
305 |
306 | var runner = new SampleTaskRunner.TaskRunner();
307 | task.Start(runner);
308 |
309 | runner.Update(20);
310 |
311 | Assert.IsTrue(task.IsCompleted);
312 | }
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/IteratorTasks/Task.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 |
5 | namespace Aiming.IteratorTasks
6 | {
7 | ///
8 | /// .NET 4 の Task 的にコルーチンを実行するためのクラス。
9 | /// 戻り値なし版。
10 | ///
11 | public partial class Task : IEnumerator, IDisposable
12 | {
13 | protected IEnumerator Routine { get; set; }
14 |
15 | private AggregateException _error;
16 |
17 | ///
18 | /// タスク中で発生した例外。
19 | ///
20 | ///
21 | /// 並行動作で、複数のタスクで同時に例外が起きたりする場合があるので、AggregateException にしてある。
22 | ///
23 | public AggregateException Error
24 | {
25 | get
26 | {
27 | var internalTask = Routine as Task;
28 | if(internalTask != null && internalTask.Error != null)
29 | {
30 | AddError(internalTask.Error);
31 | internalTask.ClearError();
32 | }
33 | return _error;
34 | }
35 | private set
36 | {
37 | _error = value;
38 | }
39 | }
40 |
41 | private List _errors;
42 |
43 | ///
44 | /// Task.Error を AggregateException にしたので、例外の追加は this.Error = exc; じゃなくて、この AddError を介して行う。
45 | /// 引数に AggregateException を渡した場合は、子要素を抜き出して統合。
46 | ///
47 | /// 追加したい例外。
48 | protected void AddError(Exception exc)
49 | {
50 | if (_error == null)
51 | {
52 | _errors = new List();
53 | _error = new AggregateException(_errors);
54 | }
55 |
56 | var agg = exc as AggregateException;
57 | if (agg != null)
58 | {
59 | foreach (var e in agg.Exceptions)
60 | {
61 | _errors.Add(e);
62 | }
63 | }
64 | else
65 | {
66 | _errors.Add(exc);
67 | }
68 | }
69 |
70 | protected void ClearError()
71 | {
72 | if (_error != null)
73 | {
74 | _errors = null;
75 | _error = null;
76 | }
77 | }
78 |
79 | #region IEnumerator
80 |
81 | // Current と MoveNext も明示的実装して隠したかったけども、それやると StartCoroutine で動かなくなるみたい。
82 |
83 | public virtual object Current
84 | {
85 | get
86 | {
87 | if (IsCanceled) return null;
88 | return Routine == null ? null : Routine.Current;
89 | }
90 | }
91 |
92 | public virtual bool MoveNext()
93 | {
94 | if (Status == TaskStatus.Created) Status = TaskStatus.Running;
95 | if (Status != TaskStatus.Running) return false;
96 | if (Routine == null) return false;
97 |
98 | bool hasNext;
99 |
100 | try
101 | {
102 | hasNext = Routine.MoveNext();
103 | }
104 | catch (Exception exc)
105 | {
106 | AddError(exc);
107 | hasNext = false;
108 | }
109 |
110 | if (!hasNext)
111 | {
112 | Complete();
113 | }
114 | return hasNext;
115 | }
116 |
117 | public void Dispose()
118 | {
119 | var d = Routine as IDisposable;
120 | if (d != null) d.Dispose();
121 | Routine = null;
122 | }
123 |
124 | void IEnumerator.Reset()
125 | {
126 | throw new NotImplementedException();
127 | }
128 |
129 | public void Start(TaskScheduler scheduler)
130 | {
131 | if (Status != TaskStatus.Created)
132 | throw new InvalidOperationException();
133 |
134 | scheduler.QueueTask(this);
135 | }
136 |
137 | public TaskStatus Status { get; private set; }
138 |
139 | public bool IsDone { get { return IsCompleted || IsCanceled || IsFaulted; } }
140 | public bool IsCompleted { get { return Status == TaskStatus.RanToCompletion; } }
141 | public bool IsCanceled { get { return Status == TaskStatus.Canceled; } }
142 | public bool IsFaulted { get { return Status == TaskStatus.Faulted; } }
143 |
144 | protected void Complete()
145 | {
146 | if (Error != null)
147 | Status = TaskStatus.Faulted;
148 | else
149 | Status = TaskStatus.RanToCompletion;
150 |
151 | if (_callback.Count != 0)
152 | {
153 | foreach (var c in _callback)
154 | {
155 | Invoke(c);
156 | }
157 | }
158 | _callback.Clear();
159 | }
160 |
161 | private void Invoke(Action c)
162 | {
163 | try
164 | {
165 | c(this);
166 | }
167 | catch (Exception exc)
168 | {
169 | AddError(exc);
170 | }
171 | }
172 |
173 | #endregion
174 |
175 | List> _callback = new List>();
176 |
177 | ///
178 | /// コルーチン完了後に呼ばれるコールバックを登録。
179 | ///
180 | /// コールバック。
181 | /// 自分自身(fluent interface)。
182 | ///
183 | /// ちなみに、callback 内で発生した例外も Error に統合されて、後段のタスクが実行されなくなる。
184 | /// その意味で、OnComplete というよりは、HookComplete (完了に割って入る)の方が正しい気も。
185 | ///
186 | public Task OnComplete(Action callback)
187 | {
188 | if (IsDone)
189 | Invoke(callback);
190 | else
191 | _callback.Add(callback);
192 |
193 | return this;
194 | }
195 |
196 | ///
197 | /// コルーチンがエラー終了(例外発生)した時だけ呼ばれるコールバックを登録。
198 | ///
199 | /// エラー処理コールバック。
200 | /// 自分自身(fluent interface)。
201 | ///
202 | /// 内部的には OnComplete を呼んでるので、挙動はそちらに準じる。
203 | ///
204 | public Task OnError(Action errorHandler)
205 | where T : Exception
206 | {
207 | return this.OnComplete(t =>
208 | {
209 | if (t.Error != null)
210 | foreach (var e in t.Error.Exceptions)
211 | {
212 | var et = e as T;
213 | if (et != null)
214 | errorHandler(et);
215 | }
216 | });
217 | }
218 |
219 | ///
220 | /// OnError、最初から IEnumerable{T} 受け取るようにしとけばよかった…
221 | /// 後からオーバーロード足そうとしたら、既存コードがエラーになったので、しょうがなく別メソッドに。
222 | /// AsOne ってのも微妙だけども、2 とか Ex とかつけるよりはマシなので。
223 | ///
224 | public Task OnErrorAsOne(Action> errorHandler)
225 | {
226 | return this.OnComplete(t =>
227 | {
228 | if (t.Error != null)
229 | errorHandler(t.Error.Exceptions);
230 | });
231 | }
232 |
233 | ///
234 | /// タスクをキャンセルします。
235 | ///
236 | public void Cancel()
237 | {
238 | if (Cancellation == null)
239 | throw new InvalidOperationException("Can't cancel Task.");
240 |
241 | Cancellation.Cancel();
242 |
243 | MoveNext();
244 | }
245 |
246 | public void Cancel(Exception e)
247 | {
248 | if (Cancellation == null)
249 | throw new InvalidOperationException("Can't cancel Task.");
250 |
251 | Cancellation.Cancel(e);
252 |
253 | MoveNext();
254 | }
255 |
256 | public void ForceCancel()
257 | {
258 | ForceCancel(new TaskCanceledException("Task force canceled."));
259 | }
260 |
261 | ///
262 | /// タスクを強制キャンセルします。OnCompleteは呼ばれません。
263 | ///
264 | public void ForceCancel(Exception e)
265 | {
266 | Status = TaskStatus.Canceled;
267 | AddError(e);
268 | this.Dispose();
269 | }
270 |
271 | protected Task()
272 | {
273 | }
274 |
275 | ///
276 | /// 最初から完了済みのタスクを生成。
277 | /// Empty とか Return 用。
278 | ///
279 | ///
280 | protected Task(TaskStatus status)
281 | {
282 | Status = status;
283 | }
284 |
285 | ///
286 | /// コルーチンを与えて初期化。
287 | ///
288 | /// コルーチン。
289 | public Task(IEnumerator routine) { Routine = routine; }
290 |
291 | ///
292 | /// コルーチン生成メソッドを与えて初期化。
293 | ///
294 | ///
295 | public Task(Func starter)
296 | {
297 | Routine = starter();
298 | }
299 |
300 | public CancellationTokenSource Cancellation { get; set; }
301 |
302 | ///
303 | /// タスクが Error を持っていたらそれを throw。
304 | ///
305 | /// 自分自身(fluent interface)。
306 | private Task Check()
307 | {
308 | if (_error != null)
309 | {
310 | throw _error;
311 | }
312 | return this;
313 | }
314 |
315 | ///
316 | /// 空タスク(作った時点で完了済み)を生成。
317 | ///
318 | /// 空タスク。
319 | public static Task Empty()
320 | {
321 | return _empty;
322 | }
323 |
324 | private readonly static Task _empty = new Task(TaskStatus.RanToCompletion);
325 |
326 | ///
327 | /// 単に値を返す(作った時点で完了済み、最初から Return の値を持つ)タスクを生成。
328 | ///
329 | /// 戻り値の型。
330 | /// 返したい値。
331 | /// 完了済みタスク。
332 | public static Task Return(T value)
333 | {
334 | return new Task(value);
335 | }
336 |
337 | public Task Select(Func selector)
338 | {
339 | return this.ContinueWithTask(() => Task.Return(selector()));
340 | }
341 | }
342 |
343 | ///
344 | /// .NET 4 の Task 的にコルーチンを実行するためのクラス。
345 | /// 戻り値あり版。
346 | ///
347 | /// 最終的に返す型。
348 | public partial class Task : Task
349 | {
350 | ///
351 | /// 最終的に返したい値。
352 | ///
353 | virtual public T Result
354 | {
355 | get
356 | {
357 | if (Error != null) throw Error;
358 | return _result;
359 | }
360 | }
361 | private T _result;
362 |
363 | internal Task() { }
364 |
365 | ///
366 | /// Task.Return 用。
367 | ///
368 | ///
369 | internal Task(T result) : base(TaskStatus.RanToCompletion)
370 | {
371 | _result = result;
372 | }
373 |
374 | ///
375 | /// コルーチン生成メソッドを与えて初期化。
376 | ///
377 | ///
378 | public Task(Func, IEnumerator> starter)
379 | {
380 | Routine = starter(r => { _result = r; });
381 | }
382 |
383 | ///
384 | /// コルーチン完了後に呼ばれるコールバックを登録。
385 | ///
386 | /// コールバック。
387 | /// 自分自身(fluent interface)。
388 | public Task OnComplete(Action> callback)
389 | {
390 | base.OnComplete(t => callback(this));
391 | return this;
392 | }
393 |
394 | ///
395 | /// Task.Check と同様。
396 | ///
397 | /// 自分自身(fluent interface)。
398 | private Task Check()
399 | {
400 | if (Error != null)
401 | {
402 | throw Error;
403 | }
404 | return this;
405 | }
406 |
407 | public Task Select(Func selector)
408 | {
409 | return this.ContinueWithTask(x => Task.Return(selector(x)));
410 | }
411 | }
412 | }
413 |
--------------------------------------------------------------------------------