gpt4 book ai didi

c# - 异步调用后跨线程异常

转载 作者:搜寻专家 更新时间:2023-10-30 21:56:27 27 4
gpt4 key购买 nike

下面的代码块只对Npgsql(不是sqlclient、sqlite、mysql、file read async)造成跨线程无效操作异常。

private async void button1_Click(object sender, EventArgs e)
{
var strBuilder = new Npgsql.NpgsqlConnectionStringBuilder()
{
Host = "localhost",
Username = "postgres",
Password = "password"
};
using (var conn = new Npgsql.NpgsqlConnection(strBuilder.ConnectionString))
{
try
{
await conn.OpenAsync();
if (conn.State ==ConnectionState.Open)
{
MessageBox.Show("Connected");
this.button1.Text = "CROSS-THREAD-With-NPGSQL";
}
}
}
}

我查看了 Npgsql 的代码并找到了这个链接: https://github.com/npgsql/npgsql/blob/2dd46e7c544caf3302ca7b89dd888a16dccf5c2c/src/Npgsql/PGUtil.cs

在文件的底部,它说:

This mechanism is used to temporarily set the current synchronization context to null while executing Npgsql code, making all await continuations execute on the thread pool. This replaces the need to place ConfigureAwait(false) everywhere, and should be used in all surface async methods, without exception.

我从 Roji(Npgsql 存储库的所有者)那里得到了相当多的解释,但我需要理解为什么我没有看到其他驱动程序的类似问题。 npgsql 临时禁用 SynchronizationContext 的方式是否被认为是最佳实践?我正在尝试查看其他驱动程序的源代码,但这需要一段时间,所以我希望我能得到一些帮助,朝着正确的方向前进。

编辑 1:Stephen Cleary 在下面给出了非常详细的答案,但我也想在这里发布一些我的发现。它可能会帮助其他人。2016 年 9 月 24 日,npgsql 将所有 ConfigureAwait(false) 替换为 NoSynchronizationContextScope。正如 Stephen 所解释的,NoSynchronizationContextScope 临时清除了调用者上下文,从而导致了此类行为。另一方面,ConfigureAwait(false) 不会做这样的事情,也不应该被替换。为了验证,我安装了 npgsql 3.1.7(09/24/16 之前的版本),我没有再看到跨线程异常。

最佳答案

Is the way npgsql temporary disabling the SynchronizationContext considered best practice?

没有。 想法 不错:null 内部方法的SynchronizationContext.Current。然而,their implementation有问题,因为它确实清除了调用者的 SynchronizationContext.Current

这是因为原始的 SynchronizationContext 必须同步恢复,而不是在 await 之后。 NoSynchronizationContextScope.Disposable 必须在 表面异步方法向其调用方返回未完成的任务之前释放。

因此,使用 this simple example :

public async Task<NpgsqlLargeObjectStream> OpenReadAsync(uint oid, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (NoSynchronizationContextScope.Enter())
return await OpenRead(oid, true);
}

操作顺序是:

  • 一些线程调用 OpenReadAsync
  • 检查了cancellationToken
  • NoSynchronizationContextScope.Enter 保存然后清除 SynchronizationContext.Current
  • OpenRead 被调用并返回未完成的任务。
  • 任务是 awaited,这会导致 OpenReadAsync 返回给它的调用者。
  • 调用线程丢失了它的SynchronizationContext

稍后,当从 OpenRead 返回的任务完成时:

  • 线程池线程被拾取以恢复执行 OpenReadAsync
  • 释放 NoSynchronizationContextScope.Disposable,将 SynchronizationContext.Current 设置为其原始值。
  • OpenReadAsync 返回的任务已完成。
  • 线程池线程现在有一个不正确的 SynchronizationContext

所以,不,我会说这完全是错误的。

这就是为什么 my SynchronizationContextSwitcher.NoContext强制你传递一个委托(delegate):这样它就可以强制同步进行处理。它的用法比较笨拙,但它必须具有正确的语义:

public Task<NpgsqlLargeObjectStream> OpenReadAsync(uint oid, CancellationToken cancellationToken) =>
SynchronizationContextSwitcher.NoContext(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
return await OpenRead(oid, true);
});

关于c# - 异步调用后跨线程异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44418761/

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