gpt4 book ai didi

c# - 为什么 awating 后的代码在不同的线程上运行(即使设置了 SynchronizationContext)?

转载 作者:行者123 更新时间:2023-12-03 21:12:31 26 4
gpt4 key购买 nike

我正在执行以下代码片段来测试我如何更改线程,在 await 之后我的代码在该线程上ing 将被调用。根据 @Stephen Cleary in this answer , 以便能够在 await 之后继续执行异步代码在同一个线程(上下文)上,我需要设置 SynchronizationContext ,我这样做了,但是,我的代码在不同的线程中继续运行。

static void Main(string[] args)
{
var mainSyncContex = new SynchronizationContext();
SynchronizationContext.SetSynchronizationContext(mainSyncContex);

Console.WriteLine($"Hello World! ThreadId: {Thread.CurrentThread.ManagedThreadId}"); // <-- In thread 1

try
{
Task.Run(async () =>
{
SynchronizationContext.SetSynchronizationContext(mainSyncContex);

Console.WriteLine($"Is there Sync Contex?: {SynchronizationContext.Current != null}");

Console.WriteLine($"Before delay. ThreadId: {Thread.CurrentThread.ManagedThreadId}"); // <-- In thread 3
await Task.Delay(1000).ConfigureAwait(true);
Console.WriteLine($"After delay. ThreadId: {Thread.CurrentThread.ManagedThreadId}"); // <-- In thread 4
throw new Exception();
});
}
catch (Exception e)
{
Console.WriteLine($"Exception: {e.Message} Catch. ThreadId: {Thread.CurrentThread.ManagedThreadId}");
}

Console.WriteLine($"Ending ThreadId: {Thread.CurrentThread.ManagedThreadId}"); // <-- In thread 1
Console.ReadKey();
}
输出:

Hello World! ThreadId: 1
Ending ThreadId: 1
Is there Sync Contex?: True
Before delay. ThreadId: 3
After delay. ThreadId: 4


为什么会这样?

最佳答案

我想根据我的理解展示一些代码,希望它可以帮助某人。
正如 aepot、dymanoid 和 Hans Passant(感谢他们)所说,使用默认 SynchronizationContext只会做 Post输入 await 之后的其余代码转至 SynchronizationContext .
我创建了一个非常基本但不是最佳的 SynchronizationContext演示基本实现的样子。我的实现将创建一个新的 Thread并运行一些 Task s 在同一个新创建的特定上下文中 Thread .
可以找到更好的实现(但非常复杂)here在 Stephen Cleary 的 GitHub 存储库中。
我的实现基本上如下所示(来自 my GitHub repository ,存储库中的代码将来可能会有所不同):

/// <summary>
/// This <see cref="SynchronizationContext"/> will call all posted callbacks in a single new thread.
/// </summary>
public class SingleNewThreadSynchronizationContext : SynchronizationContext
{
readonly Thread _workerThread;
readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _actionStatePairs = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();

/// <summary>
/// Returns the Id of the worker <see cref="Thread"/> created by this <see cref="SynchronizationContext"/>.
/// </summary>
public int ManagedThreadId => _workerThread.ManagedThreadId;

public SingleNewThreadSynchronizationContext()
{
// Creates a new thread to run the posted calls.
_workerThread = new Thread(() =>
{
try
{
while (true)
{
var actionStatePair = _actionStatePairs.Take();
SetSynchronizationContext(this);
actionStatePair.Key?.Invoke(actionStatePair.Value);
}
}
catch (ThreadAbortException)
{
Console.WriteLine($"The thread {_workerThread.ManagedThreadId} of {nameof(SingleNewThreadSynchronizationContext)} was aborted.");
}
});

_workerThread.IsBackground = true;
_workerThread.Start();
}

public override void Post(SendOrPostCallback d, object state)
{
// Queues the posted callbacks to be called in this SynchronizationContext.
_actionStatePairs.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
}

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

public override void OperationCompleted()
{
_actionStatePairs.Add(new KeyValuePair<SendOrPostCallback, object>(new SendOrPostCallback(_ => _workerThread.Abort()), null));
_actionStatePairs.CompleteAdding();
}
}
这是一个使用它的演示:
static void SingleNewThreadSynchronizationContextDemo()
{
var synchronizationContext = new SingleNewThreadSynchronizationContext();

// Creates some tasks to test that the whole calls in the tasks (before and after awaiting) will be called in the same thread.
for (int i = 0; i < 20; i++)
Task.Run(async () =>
{
SynchronizationContext.SetSynchronizationContext(synchronizationContext);
// Before yielding, the task will be started in some thread-pool thread.
var threadIdBeforeYield = Thread.CurrentThread.ManagedThreadId;
// We yield to post the rest of the task after await to the SynchronizationContext.
// Other possiblity here is maybe to start the whole Task using a different TaskScheduler.
await Task.Yield();

var threadIdBeforeAwait1 = Thread.CurrentThread.ManagedThreadId;
await Task.Delay(100);
var threadIdBeforeAwait2 = Thread.CurrentThread.ManagedThreadId;
await Task.Delay(100);

Console.WriteLine($"SynchronizationContext: thread Id '{synchronizationContext.ManagedThreadId}' | type '{SynchronizationContext.Current?.GetType()}.'");
Console.WriteLine($"Thread Ids: Before yield '{threadIdBeforeYield}' | Before await1 '{threadIdBeforeAwait1}' | Before await2 '{threadIdBeforeAwait2}' | After last await '{Thread.CurrentThread.ManagedThreadId}'.{Environment.NewLine}");
});
}

static void Main(string[] args)
{
Console.WriteLine($"Entry thread {Thread.CurrentThread.ManagedThreadId}");
SingleNewThreadSynchronizationContextDemo();
Console.WriteLine($"Exit thread {Thread.CurrentThread.ManagedThreadId}");

Console.ReadLine();
}
输出:
Entry thread 1   
Exit thread 1
SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '11' | Before await1 '5' | Before await2 '5' | After last await '5'.

SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '4' | Before await1 '5' | Before await2 '5' | After last await '5'.

SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '12' | Before await1 '5' | Before await2 '5' | After last await '5'.

SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '6' | Before await1 '5' | Before await2 '5' | After last await '5'.

SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '10' | Before await1 '5' | Before await2 '5' | After last await '5'.

SynchronizationContext: thread Id '5' | type 'SynchronizationContexts.SingleNewThreadSynchronizationContext.'
Thread Ids: Before yield '7' | Before await1 '5' | Before await2 '5' | After last await '5'.

关于c# - 为什么 awating 后的代码在不同的线程上运行(即使设置了 SynchronizationContext)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62833028/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com