3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#初心者のTask備忘録1

Posted at
C#のTaskの使い方がちょっと分かってきたので、備忘録として。

Taskを覚え始めた頃は、実行したい個所に、コピペのように下記の形式で書いていた。

var result = await Task<string[]>.Run(() =>
{
    return Directory.GetFiles(@"C:\");
}

同じ処理を何か所にも書くのがきつくなって、下記のように、Taskを1回定義して、
実行したい個所で呼び出そうとした。(動きません。)

//Taskの定義
static Task<string[]> GetFilesTask = new Task<string[]>(() => Directory.GetFiles(@"C:\"));

//呼び出し側(このままではTaskは開始しない。)
public static async Task Main(string[] args)
{
    var result = await GetFilesTask;
    foreach (var item in result)
    {
        Console.WriteLine(item);
    }
}

上記の書き方では、結果は出力されない。
理由は、awaitで結果待ちしているGetFilesTaskは、定義されているが開始されていないから。
結果、awaitが永遠に待ち続ける。
なので、下記のように開始してからawaitで結果待ちする。

static Task<string[]> GetFilesTask = new Task<string[]>(() => Directory.GetFiles(@"C:\"));

static async Task Main(string[] args)
{
    //開始してからawaitで結果待ちする。
    GetFilesTask.Start();
    var result = await GetFilesTask;

    foreach (var item in result)
    {
        Console.WriteLine(item);
    }
}

次に、タイムアウトを設定したくなった。
(つながらないネットワークドライブにアクセスしようとすると、エラーになるまで時間がかかるから)

方法は、
・ファイルを取得するタスク
・指定時間経過後に終了するタスク
の2つを定義して、どちらか一方が完了したら、もう一方をキャンセルする。
キャンセルするには、CancellationTokenSourceを使って、タスクにキャンセル指示を出す。

ただし、下記の例で要注意なのは、GetFilesTaskが1000ミリ秒以内に完了して、かつ、
ディレクトリが見つからなかった場合、GetFilesTaskが、ネットワークパスが見つからないというIOExceptionをthrowするので、catchする必要がある。

//cts1.Cancel()メソッドを実行すると、cts1.Tokenを引数に持つ全タスクに取り消しを要求出来る。
private static CancellationTokenSource cts1 = new CancellationTokenSource();

private static Task<string[]> GetFilesTask = new Task<string[]>(() =>
Directory.GetFiles(@"\\192.168.99.99\DATA$"),cts1.Token);

static async Task Main(string[] args)
{
    GetFilesTask.Start();

    try
    {
        if(await Task.WhenAny(GetFilesTask, Task.Delay(1000, cts1.Token)) == GetFilesTask)
        {
            //ファイルを取得するタスクが先に完了すると、こっちが実行される。
            //実行中のTsk.Delay()がcts1.Tokenを引数として持っているので、キャンセルされる。
            cts1.Cancel();
            Console.WriteLine("GetFilesTask完了");
        }
        else
        {
            //タイムアウトのタスクが先に完了すると、こっちが実行される。
            //実行中のGetFilesTsakがcts1.Tokenを引数として持っているので、キャンセルされる。
            cts1.Cancel();
            Console.WriteLine("Task.Delay完了");
        }
    }
    catch
    {
        //適当
        throw;
    }
}

ここまでは、GetFilesの引数を直入力していたが、引数を指定したくなった。
結果、メソッドの中で上記処理を行うようにした。

static async Task Main(string[] args)
{
    CancellationTokenSource cts1 = new CancellationTokenSource();
    try
    {
        var result = await GetFilesTask2(@"\\192.168.99.99\DATA$", 1000, cts1);
    }
    catch
    {
      //例外処理
    }
}


private static async Task<string[]> GetFilesTask2(string directory, int timeoutMilliSeconds, CancellationTokenSource cts1)
{
    var getFilesTask = new Task<string[]>(() => Directory.GetFiles(directory),cts1.Token);
    getFilesTask.Start();
    try
    {
        if (await Task.WhenAny(getFilesTask, Task.Delay(timeoutMilliSeconds, cts1.Token)) == getFilesTask)
        {
            //ファイルを取得するタスクが先に完了すると、こっちが実行される。
            //実行中のTsk.Delay()がcts1.Tokenを引数として持っているので、キャンセルされる。
            cts1.Cancel();
            Console.WriteLine("getFilesTask完了");
            return await getFilesTask;
        }
        else
        {
            //タイムアウトのタスクが先に完了すると、こっちが実行される。
            //実行中のgetFilesTsakがcts1.Tokenを引数として持っているので、キャンセルされる。
            cts1.Cancel();
            Console.WriteLine("Task.Delay完了");
            return null;
        }
    }
    catch
    {
        //適当
        throw;
    }
}

cts1をこのメソッド内だけで使うなら、
CancellationTokenSourceもローカルでよいという事で下記に変更。

private static async Task<string[]> GetFilesTask2(string directory, int timeoutMIlliSeconds)
{
    CancellationTokenSource cts1 = new CancellationTokenSource();

    var getFilesTask = new Task<string[]>(() => Directory.GetFiles(directory),cts1.Token);
    getFilesTask.Start();
    try
    {
        if (await Task.WhenAny(getFilesTask, Task.Delay(timeoutMIlliSeconds, cts1.Token)) == getFilesTask)
        {
            //ファイルを取得するタスクが先に完了すると、こっちが実行される。
            //実行中のTsk.Delay()がcts1.Tokenを引数として持っているので、キャンセルされる。
            cts1.Cancel();
            Console.WriteLine("OK");
            return await getFilesTask;
        }
        else
        {
            //タイムアウトのタスクが先に完了すると、こっちが実行される。
            //実行中のgetFilesTsakがcts1.Tokenを引数として持っているので、キャンセルされる。
            cts1.Cancel();
            Console.WriteLine("NG");
            return null;
        }
    }
    catch
    {
        //適当
        throw;
    }
}

個人的な注意点として、ファイルをコピーするタスクや変更するタスクの場合、
タイムアウトでキャンセルされると、ファイルが中途半端な状態になってしまう可能性がある。
Taskとは関係ないし当たり前の事だけど、処理結果の確認はしっかりしよう。
例えば1分くらいでタイムアウトさせたら、通信状態が悪くて想定外の未完了状態になってたり。

3
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?