c# - 如何在控制台应用程序中与ConfigureAwait(true) 进行工作交互?

标签 c# async-await synchronization

在我正在开发的一个小项目中,我需要一个组件在其初始化的同一个线程中执行组件关闭代码。但是与 WPF/Winforms/Web 中不同的是,负责处理此问题的同步上下文确实不工作。

我的猜测是缺少同步上下文是导致无法使用ConfigureAwait(true)的问题。

有人知道如何正确实现吗?

我读到this文章,但还无法理解它。也许昨天已经太晚了。

最小重现:

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

namespace ConsoleSyncContext
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}");
            await SomeBackgroundWorkAsync();
            // if this is the same thread as above the question is solved.
            Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}");
        }

        private static async Task SomeBackgroundWorkAsync()
        {
            await Task.Run(() => { });
        }
    }
}

最佳答案

正如您已经发现的,默认情况下控制台应用程序没有同步上下文,因此 ConfigureAwait 不起作用,并且 await SomePageLoad() 之后的继续将运行在随机线程池线程上。请注意,使用 async main 方法本质上等同于:

static async Task AsyncMain() { ... } // your `async Task Main method`

// real Main method generated by compiler
static void RealMain() {
    AsyncMain().GetAwaiter().GetResult(); 
}

在您的情况下,您不需要任何同步上下文。您想要的是在主线程上初始化 CefSharp 并在主线程上关闭 CefSharp。因此,您可以执行与上面相同的操作,而不是使用 async Main,但在异步方法之外初始化和关闭 Cef:

static void Main(string[] args) {
    // starting with thread 1
    Cef.Initialize(new CefSettings());
    try {
        AsyncMain(args).GetAwaiter().GetResult();
    }
    finally {
        // we are on main thread here
        Cef.Shutdown();
    }

}

static async Task AsyncMain(string[] args) {
    await SomePageLoad(); // more stuff here
}

编辑:如果您坚持使用同步上下文,那么它可以完成,但会徒劳地增加很多复杂性。我们的目标是创建同步上下文,它将在同一线程上运行所有操作。这种情况通过简单的操作队列完成,这是基本实现(不要在生产中使用它,仅作为示例提供,没有异常处理等):

class CustomSyncContext : SynchronizationContext {
    private readonly BlockingCollection<WorkItem> _queue = new BlockingCollection<WorkItem>(new ConcurrentQueue<WorkItem>());
    private readonly Thread _thread;
    public CustomSyncContext() {
        // start new thread which will handle all callbacks
        _thread = new Thread(() => {
            // set outselves as current sync context for this thread
            SynchronizationContext.SetSynchronizationContext(this);
            foreach (var item in _queue.GetConsumingEnumerable()) {
                try {
                    // execute action
                    item.Action();
                }
                finally {
                    // if this action is synchronous, signal the caller
                    item.Signal?.Set();
                }
            }
        });
        _thread.Start();
    }
    public override void Post(SendOrPostCallback d, object state) {
        // Post means acion is asynchronous, just queue and forget
        _queue.Add(new WorkItem(() => d(state), null));
    }

    public override void Send(SendOrPostCallback d, object state) {
        // Send means action is synchronous, wait on a single until our thread executes it
        using (var signal = new ManualResetEvent(false)) {
            _queue.Add(new WorkItem(() => d(state), signal));
            signal.WaitOne();
        }
    }

    public void Shutdown() {
        // signal thread that no more callbacks are expected
        _queue.CompleteAdding();
    }

    public void WaitForShutdown() {
        _thread.Join();
    }

    private class WorkItem {
        public WorkItem(Action action, ManualResetEvent signal) {
            Action = action;
            Signal = signal;
        }
        public Action Action { get; }
        public ManualResetEvent Signal { get; }
    }
}

然后你的代码就变成了:

var ctx = new CustomSyncContext();
ctx.Send(async (_) => {
    try {
        // starting with thread 1
        Cef.Initialize(new CefSettings());

        // this method returns on thread 4
        await SomePageLoad();
    }
    finally {
        Cef.Shutdown();
        // signal the context we are done, so that main thread can unblock
        ctx.Shutdown();
        Console.WriteLine("done");
    }
}, null);

ctx.WaitForShutdown();

现在您的代码在自定义同步上下文上运行,并且 await SomePageLoad(); 之后的继续将被发布到该同步上下文并由我们的线程(与启动 CefSharp 的同一线程)执行(无 ConfigureAwait(true) 是必需的,因为默认情况下它已经为 true)。请注意,我们没有取得任何有用的成果 - 我们多了一个线程,并且我们的主线程仍然被阻塞,等待整个操作完成(没有明智的方法来解决这个问题)。

编辑2:这是不需要单独线程的变体,但也好不了多少:

class CustomSyncContext : SynchronizationContext {
    private readonly BlockingCollection<WorkItem> _queue = new BlockingCollection<WorkItem>(new ConcurrentQueue<WorkItem>());
    public override void Post(SendOrPostCallback d, object state) {
        // Post means acion is asynchronous, just queue and forget
        _queue.Add(new WorkItem(() => d(state), null));
    }

    public override void Send(SendOrPostCallback d, object state) {
        // Send means action is synchronous, wait on a single until our thread executes it
        using (var signal = new ManualResetEvent(false)) {
            _queue.Add(new WorkItem(() => d(state), signal));
            signal.WaitOne();
        }
    }

    public void Shutdown() {
        // signal thread that no more callbacks are expected
        _queue.CompleteAdding();
    }

    public void Start() {
        // now we run the loop on main thread
        foreach (var item in _queue.GetConsumingEnumerable()) {
            try {
                // execute action
                item.Action();
            }
            finally {
                // if this action is synchronous, signal the caller
                item.Signal?.Set();
            }
        }
    }

    private class WorkItem {
        public WorkItem(Action action, ManualResetEvent signal) {
            Action = action;
            Signal = signal;
        }
        public Action Action { get; }
        public ManualResetEvent Signal { get; }
    }
}

static async Task Main(string[] args) {
    var ctx = new CustomSyncContext();
    // set sync context
    SynchronizationContext.SetSynchronizationContext(ctx);
    // now execute our async stuff
    var task = DoStuff().ContinueWith(x => ctx.Shutdown());
    // now run the loop of sync context on the main thread.
    // but, how do we know when to stop? Something from outside should singal that
    // in the case signal is completion of DoStuff task
    // note that most of the time main thread is still blocked while waiting for items in queue
    ctx.Start();
}

private static async Task DoStuff() {
    try {
        // starting with thread 1
        Cef.Initialize(new CefSettings());

        // this method returns on thread 4
        await SomePageLoad();
    }
    finally {
        Cef.Shutdown();
        // signal the context we are done, so that main thread can unblock
        Console.WriteLine("done");
    }
}

关于c# - 如何在控制台应用程序中与ConfigureAwait(true) 进行工作交互?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64641377/

相关文章:

c# - 深度图像帧 Kinect V2

c# - Entity Framework VS 纯 Ado.Net

c# - 从非托管 C/C++ 代码调用 C# .NET 方法

python - 如何在Python3中将异步生成器流转换为类似文件的对象?

javascript - javascript 中的 async/await 并发函数调用(链函数)

c# - 无法声明接口(interface) "async Task<myObject> MyMethod(Object myObj); "

c# - C# 中的 char * str[] 等效项

Java - 哪种数据库/技术用于大量频繁变化的键值对?

java - 抽象类中的同步方法如何工作?

multithreading - 在Metal中同步网格中的所有线程