1. 前言此文章是官方文档的翻译,由于官方文档中文版是机器翻译的,有些部分有疏漏和错误,所以本人进行了翻译供大家学习,如有问题欢迎指正。
2. 简介.NET 包含多个相互关联的类型,它们表示任意内存的连续的强类型区域。 这些方法包括:
使用 memory 和 span
3. Memory<T>和Span<T>使用准则
3.1. 所有者, 消费者和生命周期管理由于可以在各个 API 之间传送缓冲区,以及由于缓冲区有时可以从多个线程进行访问,因此请务必考虑生命周期管理。 下面介绍三个核心概念:
以下伪代码示例阐释了这三个概念。 它包括:
using System; class Program { // Write 'value' as a human-readable string to the output buffer. void WriteInt32ToBuffer(int value, Buffer buffer); // Display the contents of the buffer to the console. void DisplayBufferToConsole(Buffer buffer); // Application code static void Main() { var buffer = CreateBuffer(); try { int value = Int32.Parse(Console.ReadLine()); WriteInt32ToBuffer(value, buffer); DisplayBufferToConsole(buffer); } finally { buffer.Destroy(); } } }
3.2. Memory<T> 和所有者/消费者模型.NET Core 支持以下两种所有权模型:
使用
using System; using System.Buffers; class Example { static void Main() { IMemoryOwner<char> owner = MemoryPool<char>.Shared.Rent(); Console.Write("Enter a number: "); try { var value = Int32.Parse(Console.ReadLine()); var memory = owner.Memory; WriteInt32ToBuffer(value, memory); DisplayBufferToConsole(owner.Memory.Slice(0, value.ToString().Length)); } catch (FormatException) { Console.WriteLine("You did not enter a valid number."); } catch (OverflowException) { Console.WriteLine($"You entered a number less than {Int32.MinValue:N0} or greater than {Int32.MaxValue:N0}."); } finally { owner?.Dispose(); } } static void WriteInt32ToBuffer(int value, Memory<char> buffer) { var strValue = value.ToString(); var span = buffer.Span; for (int ctr = 0; ctr < strValue.Length; ctr++) span[ctr] = strValue[ctr]; } static void DisplayBufferToConsole(Memory<char> buffer) => Console.WriteLine($"Contents of the buffer: '{buffer}'"); } 也可以使用 using 编写此示例: using System; using System.Buffers; class Example { static void Main() { using (IMemoryOwner<char> owner = MemoryPool<char>.Shared.Rent()) { Console.Write("Enter a number: "); try { var value = Int32.Parse(Console.ReadLine()); var memory = owner.Memory; WriteInt32ToBuffer(value, memory); DisplayBufferToConsole(memory.Slice(0, value.ToString().Length)); } catch (FormatException) { Console.WriteLine("You did not enter a valid number."); } catch (OverflowException) { Console.WriteLine($"You entered a number less than {Int32.MinValue:N0} or greater than {Int32.MaxValue:N0}."); } } } static void WriteInt32ToBuffer(int value, Memory<char> buffer) { var strValue = value.ToString(); var span = buffer.Slice(0, strValue.Length).Span; strValue.AsSpan().CopyTo(span); } static void DisplayBufferToConsole(Memory<char> buffer) => Console.WriteLine($"Contents of the buffer: '{buffer}'"); } 在此代码中:
尽管
3.3. “缺少所有者” 的Memory<T> 实例无需使用
using System; class Example { static void Main() { Memory<char> memory = new char[64]; Console.Write("Enter a number: "); var value = Int32.Parse(Console.ReadLine()); WriteInt32ToBuffer(value, memory); DisplayBufferToConsole(memory); } static void WriteInt32ToBuffer(int value, Memory<char> buffer) { var strValue = value.ToString(); strValue.AsSpan().CopyTo(buffer.Slice(0, strValue.Length).Span); } static void DisplayBufferToConsole(Memory<char> buffer) => Console.WriteLine($"Contents of the buffer: '{buffer}'"); }
3.4. 使用准则因为拥有一个内存块,但打算将其传递给多个组件,其中一些组件可能同时在特定的内存块上运行,所以建立使用
下面介绍成功使用 规则 1:对于同步 API,如有可能,请使用 Span<T>(而不是 Memory<T>)作为参数。
使用类型 有时,必须使用 规则 2:如果缓冲区应为只读,则使用 ReadOnlySpan<T> 或 ReadOnlyMemory<T> 在前面的示例中, void DisplayBufferToConsole(ReadOnlyMemory<char> buffer); 事实上,如果我们结合 规则1 和 规则2 ,我们可以做得更好,并重写方法签名如下: void DisplayBufferToConsole(ReadOnlySpan<char> buffer);
规则 3:如果方法接受 Memory<T> 并返回 void,则该方法的代码中return之后不得使用 Memory<T> 实例,保证方法结束后对其使用也结束。 这与前面提到的“租约”概念相关。 返回 void 的方法对 using System; using System.Buffers; public class Example { // implementation provided by third party static extern void Log(ReadOnlyMemory<char> message); // user code public static void Main() { using (var owner = MemoryPool<char>.Shared.Rent()) { var memory = owner.Memory; var span = memory.Span; while (true) { int value = Int32.Parse(Console.ReadLine()); if (value < 0) return; int numCharsWritten = ToBuffer(value, span); Log(memory.Slice(0, numCharsWritten)); } } } private static int ToBuffer(int value, Span<char> span) { string strValue = value.ToString(); int length = strValue.Length; strValue.AsSpan().CopyTo(span.Slice(0, length)); return length; } } 如果 // !!! INCORRECT IMPLEMENTATION !!! static void Log(ReadOnlyMemory<char> message) { // Run in background so that we don't block the main thread while performing IO. Task.Run(() => { StreamWriter sw = File.AppendText(@".\input-numbers.dat"); sw.WriteLine(message); }); } 在此实现中,Log 违反了租约,因为它在 return 之后仍尝试在后台使用 有多种方法可解决此问题:
规则 4:如果方法接受 Memory<T> 并返回某个Task,则在Task转换为终止状态之前不得使用 Memory<T> 实例。 这个是 规则3 的异步版本。 以下示例是遵守此规则,按上面例子编写的 // An acceptable implementation. static Task Log(ReadOnlyMemory<char> message) { // Run in the background so that we don't block the main thread while performing IO. return Task.Run(() => { string defensiveCopy = message.ToString(); StreamWriter sw = File.AppendText(@".\input-numbers.dat"); sw.WriteLine(defensiveCopy); sw.Flush(); }); } 此处的“终止状态”表示任务转换为 completed, faulted, canceled 状态。 此指南适用于返回 规则5:如果构造函数接受Memory <T>作为参数,则假定构造对象上的实例方法是Memory<T>实例的消费者。 请看以下示例: class OddValueExtractor { public OddValueExtractor(ReadOnlyMemory<int> input); public bool TryReadNextOddValue(out int value); } void PrintAllOddValues(ReadOnlyMemory<int> input) { var extractor = new OddValueExtractor(input); while (extractor.TryReadNextOddValue(out int value)) { Console.WriteLine(value); } } 此处的 规则 6:如果一个类型具有可写的 Memory<T> 类型的属性(或等效的实例方法),则假定该对象上的实例方法是 Memory<T> 实例的消费者。 这是 规则5 的变体。之所以存在此规则,是因为假定使用了可写属性或等效方法来捕获并保留输入的 以下示例触发了此规则: class Person { // Settable property. public Memory<char> FirstName { get; set; } // alternatively, equivalent "setter" method public SetFirstName(Memory<char> value); // alternatively, a public settable field public Memory<char> FirstName; } 规则 7:如果具有
规则 8:如果 API 接口中具有 接受此类型的实例表示组件打算获取此实例的所有权。 该组件将负责根据 规则7 进行正确处理。 在方法调用完成后,将
规则 9:如果要封装同步的 p/invoke 方法,则应接受 Span<T> 作为参数 根据 规则1, using System.Runtime.InteropServices; [DllImport(...)] private static extern unsafe int ExportedMethod(byte* pbData, int cbData); public unsafe int ManagedWrapper(Span<byte> data) { fixed (byte* pbData = &MemoryMarshal.GetReference(data)) { int retVal = ExportedMethod(pbData, data.Length); /* error checking retVal goes here */ return retVal; } } 在上一示例中,如果输入 span 为空,则 public unsafe int ManagedWrapper(Span<byte> data) { fixed (byte* pbData = &MemoryMarshal.GetReference(data)) { byte dummy = 0; int retVal = ExportedMethod((pbData != null) ? pbData : &dummy, data.Length); /* error checking retVal goes here */ return retVal; } } 规则 10:如果要包装异步 p/invoke 方法,则应接受 Memory<T> 作为参数 由于 fixed 关键字不能在异步操作中使用,因此使用 using System.Runtime.InteropServices; [UnmanagedFunctionPointer(...)] private delegate void OnCompletedCallback(IntPtr state, int result); [DllImport(...)] private static extern unsafe int ExportedAsyncMethod(byte* pbData, int cbData, IntPtr pState, IntPtr lpfnOnCompletedCallback); private static readonly IntPtr _callbackPtr = GetCompletionCallbackPointer(); public unsafe Task<int> ManagedWrapperAsync(Memory<byte> data) { // setup var tcs = new TaskCompletionSource<int>(); var state = new MyCompletedCallbackState { Tcs = tcs }; var pState = (IntPtr)GCHandle.Alloc(state); var memoryHandle = data.Pin(); state.MemoryHandle = memoryHandle; // make the call int result; try { result = ExportedAsyncMethod((byte*)memoryHandle.Pointer, data.Length, pState, _callbackPtr); } catch { ((GCHandle)pState).Free(); // cleanup since callback won't be invoked memoryHandle.Dispose(); throw; } if (result != PENDING) { // Operation completed synchronously; invoke callback manually // for result processing and cleanup. MyCompletedCallbackImplementation(pState, result); } return tcs.Task; } private static void MyCompletedCallbackImplementation(IntPtr state, int result) { GCHandle handle = (GCHandle)state; var actualState = (MyCompletedCallbackState)(handle.Target); handle.Free(); actualState.MemoryHandle.Dispose(); /* error checking result goes here */ if (error) { actualState.Tcs.SetException(...); } else { actualState.Tcs.SetResult(result); } } private static IntPtr GetCompletionCallbackPointer() { OnCompletedCallback callback = MyCompletedCallbackImplementation; GCHandle.Alloc(callback); // keep alive for lifetime of application return Marshal.GetFunctionPointerForDelegate(callback); } private class MyCompletedCallbackState { public TaskCompletionSource<int> Tcs; public MemoryHandle MemoryHandle; }
|
|