using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
namespace DirectoryTask
{
class DirectoryTaskImpl : IDirectoryTask
{
int folders;
int folderTotal;
int files;
int fileTotal;
object sync = new object();
public bool Cancelation{ get; private set; }
Func<CancellationTokenSource> getSource;
Action<CancellationTokenSource> setSource;
/// <summary>
/// TokenSourceをこちらでコントロールしたいがinterfaceは変えられないのでこのような奇形設計に
/// </summary>
public DirectoryTaskImpl(Func<CancellationTokenSource> getSource, Action<CancellationTokenSource> setSource)
{
this.getSource = getSource;
this.setSource = setSource;
}
void Reset()
{
folders = folderTotal = files = fileTotal = 0;
ShowProgress();
setSource(new CancellationTokenSource());
Cancelation = false;
}
/// <summary>
/// pathがファイルならそのファイルを、フォルダなら直下のファイルを処理するStart済みのTaskが返る
/// キャンセルや例外により停止している時にはnullが返る
/// </summary>
public Task Entry(string path, CancellationToken token)
{
//キャンセル中でもnullが返らない場合がありうるが
//今のところエラーメッセージが出ないぐらいの影響しかない
if (Cancelation) return null;
if (Directory.Exists(path))
{
//フォルダならその下にあるファイルを処理する
Task r = new Task(() =>
{
//キャンセル中なのに入ってしまった場合は無駄な処理を起動しないようにする
if (token.IsCancellationRequested) return;
Interlocked.Increment(ref folders);
Interlocked.Increment(ref folderTotal);
var tasks = Directory.GetFiles(path).Select(p => TaskStart(p, token)).ToArray();
try
{
Task.WaitAll(tasks);
}
//tasksのどれかがキャンセルなり失敗したらAggregateExceptionが飛ぶ
catch (AggregateException) { return; }
Interlocked.Decrement(ref folders);
});
r.Start();
return r;
}
else
{
//ファイルならそれを処理する
Task r = TaskStart(path, token);
return r;
}
}
Task TaskStart(string path, CancellationToken token)
{
var r = new Task(() => Hoge(path, token), token);
r.ContinueWith(task => Finish(task, token));
Interlocked.Increment(ref files);
Interlocked.Increment(ref fileTotal);
ShowProgress();
r.Start();
return r;
}
void Finish(Task t, CancellationToken token)
{
//Completeが複数回呼ばれないようにロックする
lock (sync)
{
Interlocked.Decrement(ref files);
if (Cancelation)
{
//キャンセルした後もFinishが呼ばれ続ける
//それが全部終われば上のDecrementにより最終的にfilesが0になりResetが入る
if (files == 0) Reset();
return;
}
if (t.IsCanceled)
{
Cancelation = true;
DoComplete(ExitReason.Cancel);
}
else if (t.IsFaulted)
{
//例外が起きた場合
Cancelation = true;
getSource().Cancel();
//AggregateExceptionが返る。これは複数のInnerExceptionを取れるようになってるが、この局面では多分一個だけのはず
ExceptionThrown(null, new ExceptionEventArgs { Exception = t.Exception.InnerExceptions[0] });
DoComplete(ExitReason.Fatal);
}
else if (t.IsCompleted)
{
if (files == 0)
{
//全部処理し終わった
DoComplete(ExitReason.Complete);
Reset();
}
else
{
ShowProgress();
}
}
else throw new Exception("???");
}
}
void DoComplete(ExitReason er)
{
Complete(this, new ExitEventArgs { Reason = er });
}
void ShowProgress()
{
FileProgress(null, new ProgressEventArgs { Done = fileTotal - files, Total = fileTotal });
FolderProgress(null, new ProgressEventArgs { Done = folderTotal - folders, Total = folderTotal });
}
static Random Rand = new Random();
/// <summary>
/// なんか適当な処理
/// </summary>
void Hoge(string path, CancellationToken token)
{
//無駄な処理をしないようにしておく
if (token.IsCancellationRequested) return;
Thread.Sleep(1000*(Rand.Next(2) + 1));
//500回に1回例外が起こるようにしてみた
if(Rand.Next(500) == 0)
throw new Exception("Exceptionだ!");
}
public event EventHandler<ExitEventArgs> Complete;
public event EventHandler<ExceptionEventArgs> ExceptionThrown;
public event EventHandler<ProgressEventArgs> FileProgress;
public event EventHandler<ProgressEventArgs> FolderProgress;
}
}