1. ホーム
  2. c#

[解決済み] タスクの同期継続を防ぐには?

2023-05-28 07:32:56

質問

ライブラリ(ソケットネットワーク)のコードがあり、そのコードは Task -をベースにした API があります。 TaskCompletionSource<T> . しかし、TPLには、同期的な継続を防ぐことができないように見えるという厄介な点があります。私なら のように ができるようにしたいのは、どちらかです。

  • を伝える TaskCompletionSource<T> でアタッチすることを許可してはいけません。 TaskContinuationOptions.ExecuteSynchronously または
  • 結果を設定する ( SetResult / TrySetResult ) を指定する方法で TaskContinuationOptions.ExecuteSynchronously は無視され、代わりにプールを使用します。

具体的には、受信したデータは専用のリーダーで処理され、もし呼び出し側が TaskContinuationOptions.ExecuteSynchronously でアタッチできる場合、リーダーをストールさせることができます (これは自分だけでなく、他の人にも影響します)。以前は、この問題を回避するために を検出するハッカーで対処していました。 が存在するかどうかを検出し、もし存在するならば、その補完を ThreadPool しかし、これは、呼び出し元がワークキューを飽和させている場合、補完がタイムリーに処理されないため、重大な影響を及ぼします。もし、呼び出し元が Task.Wait() (など)を使用している場合、本質的にデッドロックが発生します。同様に、これは読者がワーカーを使用するのではなく、専用のスレッド上にある理由です。

私が TPL チームに口うるさく言う前に: 私はオプションを見逃していますか?

重要なポイントです。

  • 外部の呼び出し元にスレッドを乗っ取られないようにしたい
  • 私は ThreadPool は、プールが飽和したときに動作する必要があるため、実装として

以下の例は出力を生成します(タイミングにより順序が異なる場合があります)。

Continuation on: Main thread
Press [return]
Continuation on: Thread pool

問題は、ランダムな呼び出し元が "メインスレッド" で継続を得ることに成功したという事実です。実際のコードでは、これは主要な読者に割り込むことになります。

コードです。

using System;
using System.Threading;
using System.Threading.Tasks;

static class Program
{
    static void Identify()
    {
        var thread = Thread.CurrentThread;
        string name = thread.IsThreadPoolThread
            ? "Thread pool" : thread.Name;
        if (string.IsNullOrEmpty(name))
            name = "#" + thread.ManagedThreadId;
        Console.WriteLine("Continuation on: " + name);
    }
    static void Main()
    {
        Thread.CurrentThread.Name = "Main thread";
        var source = new TaskCompletionSource<int>();
        var task = source.Task;
        task.ContinueWith(delegate {
            Identify();
        });
        task.ContinueWith(delegate {
            Identify();
        }, TaskContinuationOptions.ExecuteSynchronously);
        source.TrySetResult(123);
        Console.WriteLine("Press [return]");
        Console.ReadLine();
    }
}

どのように解決するのですか?

.NET 4.6での新機能です。

.NET 4.6では、新しい TaskCreationOptions : RunContinuationsAsynchronously .


プライベートフィールドにアクセスするためにReflectionを使用することを望んでいるのですから...

TCSのTaskをマークするのは TASK_STATE_THREAD_WAS_ABORTED フラグを付けると、すべての連続がインライン化されないようになります。

const int TASK_STATE_THREAD_WAS_ABORTED = 134217728;

var stateField = typeof(Task).GetField("m_stateFlags", BindingFlags.NonPublic | BindingFlags.Instance);
stateField.SetValue(task, (int) stateField.GetValue(task) | TASK_STATE_THREAD_WAS_ABORTED);

編集します。

Reflectionのemitを使う代わりに、式を使うことを提案します。これはより可読性が高く、PCLと互換性があるという利点があります。

var taskParameter = Expression.Parameter(typeof (Task));
const string stateFlagsFieldName = "m_stateFlags";
var setter =
    Expression.Lambda<Action<Task>>(
        Expression.Assign(Expression.Field(taskParameter, stateFlagsFieldName),
            Expression.Or(Expression.Field(taskParameter, stateFlagsFieldName),
                Expression.Constant(TASK_STATE_THREAD_WAS_ABORTED))), taskParameter).Compile();

Reflectionを使用せず。

もし誰かが興味を持っているなら、私はこれをReflectionなしで行う方法を見つけ出したのですが、それは同様に少し"汚い"で、もちろん無視できないperfのペナルティを伴います。

try
{
    Thread.CurrentThread.Abort();
}
catch (ThreadAbortException)
{
    source.TrySetResult(123);
    Thread.ResetAbort();
}