Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
282 views
in Technique[技术] by (71.8m points)

c# - Synchronous or asynchronous continuation upon TaskCompletionSource.TrySetResult?

How to tell if the continuation initiated by TaskCompletionSource.TrySetResult is going to be executed synchronously or asynchronously?

For example:

// class A

void RegisterNotification(TaskCompletionSource<object> tcs)
{
    this.source.NotificationEvent += (s, eData) => 
    {
        Debug.WriteLine("A.before");
        tcs.TrySetResult(eData.Result);
        Debug.WriteLine("A.after");

        DoProcessingA();
    };   
}

// class B

async Task RequestNotificationAsync()
{
    var tcs = new TaskCompletionSource<object>();
    this.a.RegisterNotification(tcs);    

    Debug.WriteLine("B.before");
    var data = await tcs.Task;
    Debug.WriteLine("B.after");

    DoProcessingB();
}

If NotificationEvent is fired on a thread with the different synchronization context from that of where await tcs.Task took place, the debug output will be:

B.before
A.before
A.after
B.after

That is, the await tcs.Task continuation is executed asynchronously. If it is fired on the same synchronization context (or if there is no synchronization context in both places), the output will be:

B.before
A.before
B.after
A.after

That is, the continuation is executed synchronously.

Is there a way to predict this order inside RegisterNotification?

I could save SynchronizationContext.Current inside RegisterNotification and compare it later when I call tcs.TrySetResult. But that not necessarily mean that await tcs.Task will take place on the context I saved.

In theory, if I could predict this, I might be able to use it to diagnose and prevent potential deadlocks.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I don't think there is a documented way to predict synchronous/asynchronous behaviour of SetResult in advance. If you want to explicitly impose asynchronous continuation, the Task.Run(() => tcs.SetResult()) idea proposed by @Damien_The_Unbeliever is simple and universal.

However, if you really would like to reduce thread switching and still force asynchrony, you could wrap tcs.SetResult with a custom dumb SynchronizationContext. The only purpose of it would be its uniqueness as compared to the context of await tcs.Task (and to that of any other continuations possibly registered on tcs.Task). That would cause asynchronous continuation(s) on the consumer side of TaskCompletionSource, either via SynchronizationContext.Post or on a pool thread (if there's no synchronization context on the consumer side).

The test app:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinForms_21845495
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();

            this.Load += async (s, e) =>
            {
                // test on WindowsFormsSynchronizationContext
                await RequestNotificationAsync(notifyAsync: false);
                Debug.WriteLine(String.Empty);

                await RequestNotificationAsync(notifyAsync: true);
                Debug.WriteLine(String.Empty);

                // test on a pool thread
                await Task.Run(() => RequestNotificationAsync(notifyAsync: false));
                Debug.WriteLine(String.Empty);

                await Task.Run(() => RequestNotificationAsync(notifyAsync: true));
                Debug.WriteLine(String.Empty);
            };
        }

        async Task RegisterNotification(TaskCompletionSource<object> tcs, bool notifyAsync)
        {
            await Task.Delay(500);

            Debug.WriteLine("A.before");

            if (notifyAsync)
            {
                tcs.SetResultAsync(null);
            }
            else
            {
                tcs.SetResult(null);
            }

            Debug.WriteLine("A.after");
        }

        async Task RequestNotificationAsync(bool notifyAsync)
        {
            var tcs = new TaskCompletionSource<object>();
            var task = this.RegisterNotification(tcs, notifyAsync);

            Debug.WriteLine("B.before");

            var data = await tcs.Task;

            // do not yeild
            Thread.Sleep(500); 
            Debug.WriteLine("B.after");

            // yeild
            await Task.Delay(500); 
        }
    }

    public static class TaskExt
    {
        static public void SetResultAsync<T>(this TaskCompletionSource<T> tcs, T result)
        {
            FakeSynchronizationContext.Execute(() => tcs.SetResult(result));
        }

        // FakeSynchronizationContext
        class FakeSynchronizationContext : SynchronizationContext
        {
            private static readonly ThreadLocal<FakeSynchronizationContext> s_context =
                new ThreadLocal<FakeSynchronizationContext>(() => new FakeSynchronizationContext());

            private FakeSynchronizationContext() { }

            public static FakeSynchronizationContext Instance { get { return s_context.Value; } }

            public static void Execute(Action action)
            {
                var savedContext = SynchronizationContext.Current;
                SynchronizationContext.SetSynchronizationContext(FakeSynchronizationContext.Instance);
                try
                {
                    action();
                }
                finally
                {
                    SynchronizationContext.SetSynchronizationContext(savedContext);
                }
            }

            // SynchronizationContext methods

            public override SynchronizationContext CreateCopy()
            {
                return this;
            }

            public override void OperationStarted()
            {
                throw new NotImplementedException("OperationStarted");
            }

            public override void OperationCompleted()
            {
                throw new NotImplementedException("OperationCompleted");
            }

            public override void Post(SendOrPostCallback d, object state)
            {
                throw new NotImplementedException("Post");
            }

            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotImplementedException("Send");
            }
        }
    }
}

The output:

B.before
A.before
B.after
A.after

B.before
A.before
A.after
B.after

B.before
A.before
B.after
A.after

B.before
A.before
A.after
B.after

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...