How can I prevent synchronous continuations on a Task?

后端 未结 6 462
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-27 11:04

I have some library (socket networking) code that provides a Task-based API for pending responses to requests, based on TaskCompletionSource

6条回答
  •  南方客
    南方客 (楼主)
    2020-11-27 11:28

    The simulate abort approach looked really good, but led to the TPL hijacking threads in some scenarios.

    I then had an implementation that was similar to checking the continuation object, but just checking for any continuation since there are actually too many scenarios for the given code to work well, but that meant that even things like Task.Wait resulted in a thread-pool lookup.

    Ultimately, after inspecting lots and lots of IL, the only safe and useful scenario is the SetOnInvokeMres scenario (manual-reset-event-slim continuation). There are lots of other scenarios:

    • some aren't safe, and lead to thread hijacking
    • the rest aren't useful, as they ultimately lead to the thread-pool

    So in the end, I opted to check for a non-null continuation-object; if it is null, fine (no continuations); if it is non-null, special-case check for SetOnInvokeMres - if it is that: fine (safe to invoke); otherwise, let the thread-pool perform the TrySetComplete, without telling the task to do anything special like spoofing abort. Task.Wait uses the SetOnInvokeMres approach, which is the specific scenario we want to try really hard not to deadlock.

    Type taskType = typeof(Task);
    FieldInfo continuationField = taskType.GetField("m_continuationObject", BindingFlags.Instance | BindingFlags.NonPublic);
    Type safeScenario = taskType.GetNestedType("SetOnInvokeMres", BindingFlags.NonPublic);
    if (continuationField != null && continuationField.FieldType == typeof(object) && safeScenario != null)
    {
        var method = new DynamicMethod("IsSyncSafe", typeof(bool), new[] { typeof(Task) }, typeof(Task), true);
        var il = method.GetILGenerator();
        var hasContinuation = il.DefineLabel();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldfld, continuationField);
        Label nonNull = il.DefineLabel(), goodReturn = il.DefineLabel();
        // check if null
        il.Emit(OpCodes.Brtrue_S, nonNull);
        il.MarkLabel(goodReturn);
        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Ret);
    
        // check if is a SetOnInvokeMres - if so, we're OK
        il.MarkLabel(nonNull);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldfld, continuationField);
        il.Emit(OpCodes.Isinst, safeScenario);
        il.Emit(OpCodes.Brtrue_S, goodReturn);
    
        il.Emit(OpCodes.Ldc_I4_0);
        il.Emit(OpCodes.Ret);
    
        IsSyncSafe = (Func)method.CreateDelegate(typeof(Func));
    

提交回复
热议问题