none
TPLで引数を持たせたデリゲートを使うには RRS feed

  • 質問

  • C#4.0 で導入されたTPLに関する質問です.

    教えて頂きたい内容は
    「TPLで引数有のデリゲートを使用する方法」です.

    別の掲示板で質問した際に
    public Task(
    Action<Object> action,
    Object state//メソッドに渡す引数
    )
    コンストラクタを使えばいいのではないかとアドバイスを頂きました.

    そこで,最終的には,以下に記載するようなコードを導いたのですが上手くいきません.
    以下のコードは,string型を引数に1つ持つメソッドvoid Process1(string s)を10個並列実行させることを目的したプログラムです.
    (Startメソッドは省略しております)

    //public void Process1(string s){};
    Action<string> delProcess1 = this.Process1;

    Task[] tasks = new Task[10];
    for (int i = 0; i < tasks.Length; i++)
    {
    tasks[i] = new Task(delProcess1, "abc");
    }

    エラー内容は以下の通りです.
    =============================================================
    (エラー1)
    'System.Threading.Tasks.Task.Task(System.Action, System.Threading.CancellationToken)' に最も適している
    オーバーロード メソッドには無効な引数がいくつか含まれています。

    (エラー2)
    引数 1: 
    'System.Action<string>'から'System.Action' に変換できません

    (エラー3)
    引数 2: 
    'string'から'System.Threading.CancellationToken' に変換できません
    =============================================================

    また,以前の質問の途中で,
    >Action<参照型> は Action<object> と互換性がありますが、Action<値型> は互換性がないんですね。
    といったコメントも頂きました.
    そこで,並列実行するメソッドの引数を,値型のint,参照型のstring,Object型を継承した自作クラスにして試してみたのですが,エラー内容がほぼ変わりませんでした.

    エラー内容を見る限り,
    public Task(
    Action<Object> action,
    Object state
    )
    とは違うコンストラクタが呼び出されている気がするのですが,いったい,どこに問題があるのでしょうか?

    また,今回の例では並列で実行したい処理をAction<T>で指定するつもりでしたが,Func<T, TResult>でも指定することは可能でしょうか?

    アドバイスよろしくお願いします.

    2010年11月15日 13:48

回答

  • > Action<string> delProcess1 = this.Process1;

    上記箇所を以下のようにすると実行できます。

    var delProcess1 = new Action<object>(o => this.Process1((string)o));

    Task のコンストラクタが state 引数として受け取るのは object 型なため、Task から action(state) のように呼び出せるメソッドも、object 型を引数にとるものだけになります。
    (Action<in T> は反変性ですが、object 型はルートですので。)

    Task 用のメソッドとしては、通常は最初から引数を object 型にしておくものなのかなと思いました。

    > また,今回の例では並列で実行したい処理をAction<T>で指定するつもりでしたが,Func<T, TResult>でも指定することは可能でしょうか?

    いちおう、以下のように書くことができます。

    private void button1_Click(object sender, EventArgs e)
    {
        var delProcess1 = new Action<object>(
            o =>
            {
                var p = (Process1Args)o;
                p.Result = this.Process1(p.Arg);
            });

        var state = new Process1Args() { Arg = "abc" };
        var task = new System.Threading.Tasks.Task(delProcess1, state);
        task.RunSynchronously(); // task.Start(); task.Wait();

        MessageBox.Show(state.Result.ToString());
    }

    private class Process1Args
    {
        public string Arg;
        public bool Result;
    }

    private bool Process1(string arg)
    {
        return arg == "abc";
    }

    Process1 メソッドのシグネチャや仕様が変更できれば、別の方法があるのかもしれません。
    ただ、Task 自体、私はまだ把握できていないので、作法として何が適切なのかわかってません。
    ツッコミが入ればいいなと思ってます。(^^;

    • 回答としてマーク Spring96 2010年11月19日 2:00
    2010年11月16日 2:54
  • TH01さん,回答ありがとうございます.

    丁寧に教えて頂いたおかげで,問題の原因理解でき,問題を解決することができました!!

    また, task.RunSynchronously(); を知らなかったので,その点でも大変勉強になりました.

    ありがとうございます!!

     

    >いちおう、以下のように書くことができます。

    以下に書いて頂いたソースなのですが,戻り値有り/引数1つのメソッドを並列で処理したい場合,

    Task<TResult>クラスで定義されている Task<TResult>(Func<Object, TResult>, Object)というコンストラクタを使用するのはどうでしょうか?(もし,意図があって書かれていたのなら,すみません...)

    //public bool Process2(string s){ return true; }

     var delProcess = new Func<object, bool>((o) => this.Process2((string)o));

     Task<bool>[] tasks = new Task<bool>[10];

     for (int i = 0; i < tasks.Length; i++)

     {

          tasks[i] = new Task<bool>(delProcess, "abc");

          tasks[i].Start();

      }

      Task.WaitAll(tasks);

    これなら,TH01さんに教えて頂いた「戻り値無し/引数1つのメソッドを並列で処理したい場合」とほぼ同様の記述が可能です.

    ただ,戻り値が有る/無い,どちらの場合でも,引数が複数個ある場合には,この方法では記述で無いようなので,

    その場合には,TH01さんが書いてくださったサンプルコードのように引数をクラスで纏めておく必要があるようです・・・.

    上記のコードについて指摘/コメント等を頂ければ,すごく,勉強になります.

     

     

     

     

     

     

    • 回答としてマーク Spring96 2010年11月19日 2:00
    2010年11月16日 19:07

すべての返信

  • 関連する話題としてリンクをつなげておきます。
    投稿される方は参考にしてみてください。

    http://bbs.wankuma.com/index.cgi?mode=al2&namber=54984
    http://bbs.wankuma.com/index.cgi?mode=al2&namber=55037


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年11月15日 14:55
    モデレータ
  • > Action<string> delProcess1 = this.Process1;

    上記箇所を以下のようにすると実行できます。

    var delProcess1 = new Action<object>(o => this.Process1((string)o));

    Task のコンストラクタが state 引数として受け取るのは object 型なため、Task から action(state) のように呼び出せるメソッドも、object 型を引数にとるものだけになります。
    (Action<in T> は反変性ですが、object 型はルートですので。)

    Task 用のメソッドとしては、通常は最初から引数を object 型にしておくものなのかなと思いました。

    > また,今回の例では並列で実行したい処理をAction<T>で指定するつもりでしたが,Func<T, TResult>でも指定することは可能でしょうか?

    いちおう、以下のように書くことができます。

    private void button1_Click(object sender, EventArgs e)
    {
        var delProcess1 = new Action<object>(
            o =>
            {
                var p = (Process1Args)o;
                p.Result = this.Process1(p.Arg);
            });

        var state = new Process1Args() { Arg = "abc" };
        var task = new System.Threading.Tasks.Task(delProcess1, state);
        task.RunSynchronously(); // task.Start(); task.Wait();

        MessageBox.Show(state.Result.ToString());
    }

    private class Process1Args
    {
        public string Arg;
        public bool Result;
    }

    private bool Process1(string arg)
    {
        return arg == "abc";
    }

    Process1 メソッドのシグネチャや仕様が変更できれば、別の方法があるのかもしれません。
    ただ、Task 自体、私はまだ把握できていないので、作法として何が適切なのかわかってません。
    ツッコミが入ればいいなと思ってます。(^^;

    • 回答としてマーク Spring96 2010年11月19日 2:00
    2010年11月16日 2:54
  • TH01さん,回答ありがとうございます.

    丁寧に教えて頂いたおかげで,問題の原因理解でき,問題を解決することができました!!

    また, task.RunSynchronously(); を知らなかったので,その点でも大変勉強になりました.

    ありがとうございます!!

     

    >いちおう、以下のように書くことができます。

    以下に書いて頂いたソースなのですが,戻り値有り/引数1つのメソッドを並列で処理したい場合,

    Task<TResult>クラスで定義されている Task<TResult>(Func<Object, TResult>, Object)というコンストラクタを使用するのはどうでしょうか?(もし,意図があって書かれていたのなら,すみません...)

    //public bool Process2(string s){ return true; }

     var delProcess = new Func<object, bool>((o) => this.Process2((string)o));

     Task<bool>[] tasks = new Task<bool>[10];

     for (int i = 0; i < tasks.Length; i++)

     {

          tasks[i] = new Task<bool>(delProcess, "abc");

          tasks[i].Start();

      }

      Task.WaitAll(tasks);

    これなら,TH01さんに教えて頂いた「戻り値無し/引数1つのメソッドを並列で処理したい場合」とほぼ同様の記述が可能です.

    ただ,戻り値が有る/無い,どちらの場合でも,引数が複数個ある場合には,この方法では記述で無いようなので,

    その場合には,TH01さんが書いてくださったサンプルコードのように引数をクラスで纏めておく必要があるようです・・・.

    上記のコードについて指摘/コメント等を頂ければ,すごく,勉強になります.

     

     

     

     

     

     

    • 回答としてマーク Spring96 2010年11月19日 2:00
    2010年11月16日 19:07
  • > (もし,意図があって書かれていたのなら,すみません...)

    もちろん、深い意図が・・・、知らなかっただけです。(--;
    そんなのがあればいいのに、と思いつつ、ちゃんと調べていませんでした。
    教えていただき、ありがとうございます。
    その場合の結果は tasks[i].Result で得られるようですね。

    > その場合には,TH01さんが書いてくださったサンプルコードのように引数をクラスで纏めておく必要があるようです・・・.

    これまでの Thread でも同じでしたし、out な引数にも対処できるのでよいことだと思います。
    引数用のクラスとして、Dictionary や Tuple や dynamic な ExpandoObject(または匿名型)の利用も場合によっては効果的かもしれませんね。
    さらに、次のようなメソッドを対に用意するのもいいかなと思いました。
    private void Process1Wrapper(object state)
    {
        state から本来の引数に分解。
        Process1(本来の引数);
    }
    一般的にはどうするんでしょうね。

    2010年11月17日 1:03
  • TH01さん,回答ありがとうございます.

    おかげさまで,最初の議題は解決しましたので,これで回答を締め切りたいと思います.

    ご丁寧に教えて頂きありがとうございました!!

    2010年11月19日 1:58