已编辑,根据MSDN:
线程安全。 SHCreateMemStream 创建的流是线程安全的
从 Windows 8 开始。在早期系统上,流不是线程安全的。
CreateStreamOnHGlobal 创建的流是线程安全的。
我有两个相反的答案,所以我决定验证一下。 看起来@HansPassant 是对的,而@IInspectable 是错的。至少,在另一个线程上为原始IStream 对象创建了一个代理。
测试用例显示如下:
即使两个线程都属于不同的单元,对IStream 的直接引用仍然可以跨线程使用。它只是工作。
如果两个线程都是 MTA 线程,则来自 thread 的 IStream 在 thread2 上解组为完全相同的 IUnknown 指针,没有代理。
如果 thread1 是 STA,而 thread2 是 MTA,则存在代理。但是在thread 上创建的直接引用仍然适用于thread2。
请注意如何在来自不同线程的紧密循环内同时对stream1 执行读取和写入。当然,这没什么意义,通常有锁来同步读/写。然而,证明CreateStreamOnHGlobal 返回的IStream 对象是真正的线程安全的。
我不确定这是否是任何 IStream 实现的官方 COM 要求,正如 that Dr. Dobb's article 所建议的那样 - 很可能它只是特定于 CreateStreamOnHGlobal.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
static void TestStream()
{
// start thread1
var thread1 = new Thread(() =>
{
// create stream1 on thread1
System.Runtime.InteropServices.ComTypes.IStream stream1;
CreateStreamOnHGlobal(IntPtr.Zero, true, out stream1);
IntPtr unkStream1 = Marshal.GetIUnknownForObject(stream1);
// marshal stream1, to be unmarshalled on thread2
Guid iid = typeof(System.Runtime.InteropServices.ComTypes.IStream).GUID;
System.Runtime.InteropServices.ComTypes.IStream marshallerStream;
CoMarshalInterThreadInterfaceInStream(ref iid, stream1, out marshallerStream);
// write to stream1
var buf1 = new byte[] { 1, 2, 3, 4 };
stream1.Write(buf1, buf1.Length, IntPtr.Zero);
// start thread2
var thread2 = new Thread(() =>
{
// read from stream1 (the direct reference) on thread2
var buf2 = new byte[buf1.Length];
for (var i = 0; i < 10000; i++)
{
stream1.Seek(0, 0, IntPtr.Zero);
stream1.Read(buf2, buf2.Length, IntPtr.Zero);
// trule thread safe, this always works!
for (var j = 0; j < buf2.Length; j++)
Debug.Assert(buf1[j] == buf2[j]);
}
// Unmarshal and compare IUnknown pointers
object stream2;
CoGetInterfaceAndReleaseStream(marshallerStream, ref iid, out stream2);
IntPtr unkStream2 = Marshal.GetIUnknownForObject(stream2);
// Bangs if thread1 is STA, works OK if thread1 is MTA
Debug.Assert(unkStream1 == unkStream2);
Marshal.Release(unkStream2);
});
for (var i = 0; i < 10000; i++)
{
stream1.Seek(0, 0, IntPtr.Zero);
stream1.Write(buf1, buf1.Length, IntPtr.Zero);
}
thread2.SetApartmentState(ApartmentState.MTA);
thread2.Start();
thread2.Join();
Marshal.Release(unkStream1);
});
thread1.SetApartmentState(ApartmentState.STA);
thread1.Start();
thread1.Join();
}
static void Main(string[] args)
{
TestStream();
}
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CreateStreamOnHGlobal(
IntPtr hGlobal,
bool fDeleteOnRelease,
[Out] out System.Runtime.InteropServices.ComTypes.IStream pStream);
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CoMarshalInterThreadInterfaceInStream(
[In] ref Guid riid,
[MarshalAs(UnmanagedType.IUnknown)] object unk,
out System.Runtime.InteropServices.ComTypes.IStream stream);
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CoGetInterfaceAndReleaseStream(
[In] System.Runtime.InteropServices.ComTypes.IStream stream,
[In] ref Guid riid,
[Out, MarshalAs(UnmanagedType.IUnknown)] out object unk);
}
}