C# 如何在控制台应用程序中获得与ConfigureWait(true)的工作交互?
在我正在处理的一个小项目中,我需要一个组件在初始化它的同一个线程中执行一个组件关闭代码。但是,与WPF/Winforms/Web中不同的是,负责此操作的synchronizationcontext不起作用 我的猜测是,缺少同步上下文是导致ConfigureWait利用率不足的问题(true) 有人知道如何正确地实现这一点吗 我读了这篇文章,但还没看懂。也许昨天太晚了 最小复制:C# 如何在控制台应用程序中获得与ConfigureWait(true)的工作交互?,c#,async-await,synchronization,C#,Async Await,Synchronization,在我正在处理的一个小项目中,我需要一个组件在初始化它的同一个线程中执行一个组件关闭代码。但是,与WPF/Winforms/Web中不同的是,负责此操作的synchronizationcontext不起作用 我的猜测是,缺少同步上下文是导致ConfigureWait利用率不足的问题(true) 有人知道如何正确地实现这一点吗 我读了这篇文章,但还没看懂。也许昨天太晚了 最小复制: using System; using System.Threading; using System.Threadin
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(true)
,因为这意味着您需要返回不存在的原始scheduler/context
自定义实现
一个非常简单的实现应该做到这一点。基本上有两个步骤
正如您已经了解到的,控制台应用程序默认情况下没有同步上下文,因此
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,而是在async方法之外初始化并关闭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
}
编辑:如果您坚持使用同步上下文,那么它是可以做到的,但是会毫无代价地增加很多复杂性。我们的目标是创建同步上下文,它将在同一线程上运行所有操作。这种情况可以通过简单的操作队列完成,以下是基本实现(不要在生产中使用它,仅作为示例提供,没有异常处理等):
现在,您的代码在自定义同步上下文上运行,并在之后继续等待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");
}
}
类CustomSyncContext:SynchronizationContext{
private readonly BlockingCollection _queue=new BlockingCollection(new ConcurrentQueue());
公共重写void Post(sendorpostd,对象状态){
//Post意味着acion是异步的,只需排队就可以了
_添加(新工作项(()=>d(状态),null));
}
公共覆盖无效发送(SendorPostD,对象状态){
//发送意味着动作是同步的,请等待单个线程执行它
使用(var信号=新手动复位事件(错误)){
_添加(新工作项(()=>d(状态),信号));
signal.WaitOne();
}
}
公共空间关闭(){
//通知线程不需要更多回调
_queue.CompleteAdding();
}
公开作废开始(){
//现在我们在主线程上运行循环
foreach(变量项在_queue.GetConsumingEnumerable()中){
试一试{
//执行操作
项目.行动();
}
最后{
//如果此操作是同步的,请向调用者发送信号
item.Signal?.Set();
}
}
}
私有类工作项{
公共工作项(动作、手动复位事件信号){
行动=行动;
信号=信号;
}
公共行动行动{get;}
公共手动重置事件信号{get;}
}
}
静态异步任务主(字符串[]args){
var ctx=新的CustomSyncContext();
//设置同步上下文
SynchronizationContext.SetSynchronizationContext(ctx);
//现在执行我们的异步程序
var task=DoStuff().ContinueWith(x=>ctx.Shutdown());
//现在在主线程上运行同步上下文循环。
//但是,我们怎么知道什么时候该停下来呢?应该有来自外界的信号
//在这种情况下,信号是完成DoStuff任务
//请注意,在等待队列中的项目时,主线程大部分时间仍然处于阻塞状态
ctx.Start();
}
私有静态异步任务DoStuff(){
试一试{
//从线程1开始
初始化(新的CefSettings());
//此方法在线程4上返回
等待SomePageLoad();
}
最后{
Cef.Shutdown();
//向上下文发送我们已经完成的信号,以便主线程可以解除阻塞
控制台。写入线(“完成”);
}
}
“我有必要让一个组件在其初始化所在的同一线程中执行一个组件关闭代码”-解释为什么需要这个?也可以用你的话定义组件组件抛出一个异常只是为了我的信息,这是什么类型的组件?CefSharp.offscreen你可以将这个粘贴到问题中,也可以粘贴你得到的确切异常尽管这可能会起作用,但我的直觉是这不需要贪婪,但是没有任何例子,我只是试图回答手头的问题,希望它能帮助真正需要它的人。不会在同一线程上返回。我将添加一些简短的重新编译代码添加。感谢您分享实现。它看起来也适用于我,也许是因为遗漏了一些小的东西。这个自定义实现无法适用于OP
static async Task AsyncMain() { ... } // your `async Task Main method`
// real Main method generated by compiler
static void RealMain() {
AsyncMain().GetAwaiter().GetResult();
}
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();
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");
}
}