使用SqlBulkCopy WriteToServerAsync与CsvDataReader时遇同步操作异常的解决咨询
解决SqlBulkCopy异步写入时CsvDataReader引发的同步IO异常
你的判断没错:这个异常确实是因为CsvHelper的CsvDataReader没有实现异步的ReadAsync方法,当SqlBulkCopy.WriteToServerAsync执行时,底层会调用同步的Read操作,而当前运行环境(比如ASP.NET Core)默认禁用了同步IO,因此抛出错误。下面是几种可行的解决办法:
方法一:临时开启AllowSynchronousIO
这是最快的临时解决方案,适合测试或快速修复场景,但不推荐长期在生产环境使用(同步IO可能影响并发性能)。
全局配置(ASP.NET Core)
在Program.cs中添加以下配置:
// 针对Kestrel服务器 builder.Services.Configure<KestrelServerOptions>(options => { options.AllowSynchronousIO = true; }); // 如果使用IIS托管,需额外配置 builder.Services.Configure<IISServerOptions>(options => { options.AllowSynchronousIO = true; });
单个请求临时配置
如果不想全局开启,可以在当前请求上下文里临时允许同步IO:
HttpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
方法二:给CsvDataReader套异步包装器
自己实现一个IDataReader的异步包装类,把同步的Read操作包装成异步任务,绕过同步IO限制。注意这只是将同步操作移到线程池,并非真正的异步IO,但能解决当前异常:
public class AsyncDataReaderWrapper : IDataReader { private readonly IDataReader _innerReader; public AsyncDataReaderWrapper(IDataReader innerReader) { _innerReader = innerReader; } // 实现IDataReader所有同步成员,直接委托给内部Reader public int Depth => _innerReader.Depth; public bool IsClosed => _innerReader.IsClosed; public int RecordsAffected => _innerReader.RecordsAffected; public object this[int index] => _innerReader[index]; public object this[string name] => _innerReader[name]; public void Close() => _innerReader.Close(); public void Dispose() => _innerReader.Dispose(); public bool GetBoolean(int index) => _innerReader.GetBoolean(index); public byte GetByte(int index) => _innerReader.GetByte(index); public long GetBytes(int index, long fieldOffset, byte[] buffer, int bufferoffset, int length) => _innerReader.GetBytes(index, fieldOffset, buffer, bufferoffset, length); public char GetChar(int index) => _innerReader.GetChar(index); public long GetChars(int index, long fieldoffset, char[] buffer, int bufferoffset, int length) => _innerReader.GetChars(index, fieldoffset, buffer, bufferoffset, length); public IDataReader GetData(int index) => _innerReader.GetData(index); public string GetDataTypeName(int index) => _innerReader.GetDataTypeName(index); public DateTime GetDateTime(int index) => _innerReader.GetDateTime(index); public decimal GetDecimal(int index) => _innerReader.GetDecimal(index); public double GetDouble(int index) => _innerReader.GetDouble(index); public Type GetFieldType(int index) => _innerReader.GetFieldType(index); public float GetFloat(int index) => _innerReader.GetFloat(index); public Guid GetGuid(int index) => _innerReader.GetGuid(index); public short GetInt16(int index) => _innerReader.GetInt16(index); public int GetInt32(int index) => _innerReader.GetInt32(index); public long GetInt64(int index) => _innerReader.GetInt64(index); public string GetName(int index) => _innerReader.GetName(index); public int GetOrdinal(string name) => _innerReader.GetOrdinal(name); public string GetString(int index) => _innerReader.GetString(index); public object GetValue(int index) => _innerReader.GetValue(index); public int GetValues(object[] values) => _innerReader.GetValues(values); public bool IsDBNull(int index) => _innerReader.IsDBNull(index); public bool NextResult() => _innerReader.NextResult(); public bool Read() => _innerReader.Read(); // 实现异步方法,用Task.Run包装同步操作 public Task<bool> ReadAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return Task.Run(() => _innerReader.Read(), cancellationToken); } public Task<bool> NextResultAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return Task.Run(() => _innerReader.NextResult(), cancellationToken); } }
使用时将CsvDataReader包装进去:
using var csvReader = new CsvReader(reader, config); using var csvDataReader = new CsvDataReader(csvReader); using var asyncReader = new AsyncDataReaderWrapper(csvDataReader); using (var bulkCopy = new SqlBulkCopy(sqlConnection, SqlBulkCopyOptions.UseInternalTransaction | SqlBulkCopyOptions.KeepNulls, null)) { bulkCopy.DestinationTableName = tableName; bulkCopy.BatchSize = 4000; bulkCopy.EnableStreaming = true; await bulkCopy.WriteToServerAsync(asyncReader, cancellationToken).ConfigureAwait(false); }
方法三:检查CsvHelper新版本支持
可以查看CsvHelper的最新版本,确认是否已原生支持异步IDataReader实现。不过截至目前,CsvDataReader仍未提供原生异步方法,这个方案暂时不可行,但可以持续关注官方更新。
内容的提问来源于stack exchange,提问作者JohnDiGriz
相关产品推荐
相关产品推荐

