├── 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 | --------------------------------------------------------------------------------