LoginSignup
5
5

More than 3 years have passed since last update.

C#でコールバック処理をPromiseる

Posted at

javascriptの場合

javascript には Promise という、コールバック地獄を緩和する仕組みが(最近は)あります。

例えば、 setTimeout はコールバックで指定したfunctionを呼んでくれます。

setTimeout( function(){ console.log("Hello");} , 5000); // 5000ミリ秒後に "Hello" と出力される

「じゃぁ、5秒後に"Hello"って出力した後、さらに4秒後に"good"って出力して、さらに3秒後に"by"って出したいな」

setTimeout(function() {
    console.log("Hello");
    setTimeout(function() {
        console.log("good");
        setTimeout(function() {
            console.log("by");
        },3000); //3000ミリ秒後に "by" と出力される
    },4000); //4000ミリ秒後に "good" と出力される
}, 5000); // 5000ミリ秒後に "Hello" と出力される

はい。死ねますね。 これが俗にいう「コールバック地獄」というやつです。

これを、Proimse でラップしてあげる(+最近のJavaScriptならasync~awaitで待ち受けたりすると)

function sleep(waitms) {
    return new Promise(function(resolve)
    {
        setTimeout(function() {
            resolve();
        }, waitms);
    });
}

async function Test(){
    await sleep(5000); //中で発火される setTimeout が 5000ms後にPromiseのresolve()を呼ぶ=終了とみなす
    console.log("Hello");
    await sleep(4000);
    console.log("good");
    await sleep(3000);
    console.log("by");
}

はい。めっちゃ見やすくなりましたね。

C#の場合

非同期の結果通知がコールバックしかないような処理をTaskとして待ち受けられる Promiseクラスを作ってみました。

using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

public class Promise<TResult> : IDisposable
{
    public delegate void PromiseResolve(Action<TResult> resolve);

    public delegate void PromiseResolveReject(Action<TResult> resolve, Action<Exception> reject);

    private readonly TaskCompletionSource<TResult> tcs;
    private readonly PromiseResolve unRegistResolve;

    private readonly PromiseResolveReject unRegistResolveReject;

    public Promise(PromiseResolveReject registCallback, CancellationToken ct = default) : this(registCallback, null, ct)
    {
    }

    public Promise(PromiseResolve registCallback, CancellationToken ct = default) : this(registCallback, null, ct)
    {
    }

    public Promise(PromiseResolveReject registCallback, PromiseResolveReject unRegistCallback,
        CancellationToken ct = default)
    {
        tcs = new TaskCompletionSource<TResult>();
        if (ct != default) ct.Register(() => tcs.TrySetCanceled(ct));
        registCallback(Resolve, Reject);
        unRegistResolveReject = unRegistCallback;
    }

    public Promise(PromiseResolve registCallback, PromiseResolve unRegistCallback, CancellationToken ct = default)
    {
        tcs = new TaskCompletionSource<TResult>();
        if (ct != default) ct.Register(() => tcs.TrySetCanceled(ct));
        registCallback(Resolve);
        unRegistResolve = unRegistCallback;
    }

    public void Dispose()
    {
        if (unRegistResolve != null)
            unRegistResolve(Resolve);
        else
            unRegistResolveReject?.Invoke(Resolve, Reject);
    }

    public TaskAwaiter<TResult> GetAwaiter()
    {
        return tcs.Task.GetAwaiter();
    }

    private void Resolve(TResult result)
    {
        tcs.TrySetResult(result);
    }

    private void Reject(Exception ex)
    {
        tcs.TrySetException(ex);
    }
}

イベントの追加と削除のあたりがなんかダメっぽいですね・・・。

5
5
0

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
5
5