线程和 SqlFileStream。进程无法访问指定的文件,因为它已在另一个事务中打开

Posted

技术标签:

【中文标题】线程和 SqlFileStream。进程无法访问指定的文件,因为它已在另一个事务中打开【英文标题】:Threading and SqlFileStream. The process cannot access the file specified because it has been opened in another transaction 【发布时间】:2015-07-22 13:01:21 【问题描述】:

我正在提取 SQL 文件表中文件的内容。如果我不使用 Parallel,则以下代码有效。

同时读取 sql 文件流(并行)时出现以下异常。

进程无法访问指定的文件,因为它已在另一个事务中打开。

TL;DR:

在 Parallel.ForEach 中从 FileTable(使用 GET_FILESTREAM_TRANSACTION_CONTEXT)读取文件时,出现上述异常。

供您试用的示例代码:

https://gist.github.com/NerdPad/6d9b399f2f5f5e5c6519

加长版:

获取附件并提取内容:

var documents = new List<ExtractedContent>();
using (var ts = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))

    var attachments = await dao.GetAttachmentsAsync();

    // Extract the content simultaneously
    // documents = attachments.ToDbDocuments().ToList(); // This works
    Parallel.ForEach(attachments, a => documents.Add(a.ToDbDocument())); // this doesn't

    ts.Complete();

DAO 读取文件表:

public async Task<IEnumerable<SearchAttachment>> GetAttachmentsAsync()

    try
    
        var commandStr = "....";

        IEnumerable<SearchAttachment> attachments = null;
        using (var connection = new SqlConnection(this.DatabaseContext.Database.Connection.ConnectionString))
        using (var command = new SqlCommand(commandStr, connection))
        
            connection.Open();

            using (var reader = await command.ExecuteReaderAsync())
            
                attachments = reader.ToSearchAttachments().ToList();
            
        

        return attachments;
    
    catch (System.Exception)
    
        throw;
    

为每个文件创建对象: 该对象包含对 GET_FILESTREAM_TRANSACTION_CONTEXT 的引用

public static IEnumerable<SearchAttachment> ToSearchAttachments(this SqlDataReader reader)

    if (!reader.HasRows)
    
        yield break;
    

    // Convert each row to SearchAttachment
    while (reader.Read())
    
        yield return new SearchAttachment
        
            ...
            ...
            UNCPath = reader.To<string>(Constants.UNCPath),
            ContentStream = reader.To<byte[]>(Constants.Stream) // GET_FILESTREAM_TRANSACTION_CONTEXT() 
            ...
            ...
        ;
    

使用 SqlFileStream 读取文件: 此处抛出异常

public static ExtractedContent ToDbDocument(this SearchAttachment attachment)

    // Read the file
    // Exception is thrown here
    using (var stream = new SqlFileStream(attachment.UNCPath, attachment.ContentStream, FileAccess.Read, FileOptions.SequentialScan, 4096))
    
        ...
        // extract content from the file
    

    ....

更新 1:

根据this 文章,这似乎是一个隔离级别问题。有没有人遇到过类似的问题?

【问题讨论】:

尝试在执行其余 SQL 的同一线程上打开文件。也许这根本是不允许的。 您在多个线程上写信给documentsList&lt;T&gt; 不是线程安全的,您不能这样做(这不太可能是您问题的根源,但这是一个问题)跨度> 【参考方案1】:

交易不流入Parallel.ForEach,您必须手动将交易引入。

//Switched to a thread safe collection.
var documents = new ConcurrentQueue<ExtractedContent>();
using (var ts = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))

    var attachments = await dao.GetAttachmentsAsync();
    //Grab a reference to the current transaction.
    var transaction = Transaction.Current;
    Parallel.ForEach(attachments, a =>
    
        //Spawn a dependant clone of the transaction
        using (var depTs = transaction.DependentClone(DependentCloneOption.RollbackIfNotComplete))
        
            documents.Enqueue(a.ToDbDocument());
            depTs.Complete();
        
    );

    ts.Complete();

我还从List&lt;ExtractedContent&gt; 切换到ConcurrentQueue&lt;ExtractedContent&gt;,因为不允许您同时从多个线程调用列表中的.Add(

【讨论】:

假设事务被复制过来,并发SQL文件流访问安全吗? @usr 是的,这就是使用SQL filestreams 的好处之一,他们可以参与交易。实际文件访问是通过 UNC 网络共享对在事务生命周期中存在的临时路径进行的。这与将两个只读FileStream 对象打开到同一网络路径没有什么不同。 @usr 出于好奇,我检查了参考源。 SqlFileStream 只是普通 FileStream 的包装器和 FILE_FULL_EA_INFORMATION 的托管包装器(传递给构造函数的 byte[] 是该结构的数据) 假设 SQL Server 文件共享可以同时访问同一个文件,这很有可能。 只是为了确定我再次验证了数据。集合中没有重复项。所以每个线程应该访问不同的文件。

以上是关于线程和 SqlFileStream。进程无法访问指定的文件,因为它已在另一个事务中打开的主要内容,如果未能解决你的问题,请参考以下文章

线程的创建和运行

Windows常见面试题——进程线程

线程安全的了解

进程间通信和线程间通信

Wpf中“由于其他线程拥有此对象,因此调用线程无法对其进行访问”

进程间通信和线程间通信