iar = destination.BeginWrite(buffer, 0, numRead, writeResult =>
{
if (!writeResult.CompletedSynchronously)
{
try
{
destination.EndWrite(writeResult);
readWriteLoop(null);
}
catch (Exception e2)
{
ar.Complete(e);
callback?.Invoke(ar);
}
}
}, null);
if (!iar.CompletedSynchronously)
{
return;
}
destination.EndWrite(iar);
}
}
}
catch (Exception e)
{
ar.Complete(e);
callback?.Invoke(ar);
}
};
readWriteLoop(null);
return ar;
}
public void EndCopyStreamToStream(IAsyncResult asyncResult)
{
if (asyncResult is not MyAsyncResult ar)
{
throw new ArgumentException(null, nameof(asyncResult));
}
ar.Wait();
}
private sealed class MyAsyncResult : IAsyncResult
{
private bool _completed;
private int _completedSynchronously;
private ManualResetEvent? _event;
private Exception? _error;
public MyAsyncResult(object? state) => AsyncState = state;
public object? AsyncState { get; }
public void Complete(Exception? error)
{
lock (this)
{
_completed = true;
_error = error;
_event?.Set();
}
}
public void Wait()
{
WaitHandle? h = null;
lock (this)
{
if (_completed)
{
if (_error is not null)
{
throw _error;
}
return;
}
h = _event ??= new ManualResetEvent(false);
}
h.WaitOne();
if (_error is not null)
{
throw _error;
}
}
public WaitHandle AsyncWaitHandle
{
get
{
lock (this)
{
return _event ??= new ManualResetEvent(_completed);
}
}
}
public bool CompletedSynchronously
{
get
{
lock (this)
{
if (_completedSynchronously == 0)
{
_completedSynchronously = _completed ? 1 : -1;
}
return _completedSynchronously == 1;
}
}
}
public bool IsCompleted
{
get
{
lock (this)
{
return _completed;
}
}
}
}
复制代码
哇哦,即使有了所有那些乱七八糟的东西,它仍然不是一个很好的实现。例如,实现对每个操作进行了锁定,而不是以更加无锁的方式进行,异常是原始存储的,而不是作为ExceptionDispatchInfo存储,这将使其在传播时增强其调用堆栈,每个单独操作都需要进行大量的分配(例如,为每个调用分配一个委托),等等。
现在,想象一下你必须为要编写的每个方法都做所有这些工作。每次你想编写一个可重用的方法来使用另一个异步操作时,你都需要做所有这些工作。如果你想编写可重用的组合器,可以有效地操作多个离散的 (类似于 Task.WhenAll),那就是另一层难度;每个操作实现和公开其自己的特定于该操作的 API 意味着没有共同语言可以以类似的方式谈论它们(尽管一些开发人员编写了试图通过另一层回调来减轻负担的库,这使得 API 可以向 Begin 方法提供适当的 )。
所有这些复杂性意味着很少有人尝试这样做,对于那些尝试的人来说,出现错误很常见。公平地说,这不是对APM模式的批评。相反,这是对基于回调的异步性的一种批评。
我们都习惯了现代语言中控制流构造提供给我们的强大和简单性,而基于回调的方法一旦引入任何合理的复杂性,通常会违反这种结构。其他主流语言也没有更好的替代方案。
我们需要一种更好的方式,一种从APM模式中学习、吸收其正确之处并避免其缺点的方式。有趣的是,APM模式只是一种模式;运行时、核心库和编译器没有提供任何帮助来使用或实现该模式。
基于事件的异步模式
缓存本身有点有趣。对象池可能是一个好主意,也可能是一个坏主意。对象的创建成本越高,对它们进行池化的价值就越大;例如,池化非常大的数组比池化非常小的数组更有价值,因为较大的数组不仅需要更多的CPU周期和内存访问来清零,而且会对垃圾回收器产生更多的压力,导致更频繁的垃圾回收。然而,对于非常小的对象,池化它们可能是一个净负面效应。池本身仅仅是内存分配器,垃圾回收器也是内存分配器,因此,在进行池化时,您正在用一个分配器的成本来换取另一个分配器的成本,而垃圾回收器非常擅长处理大量的小型短寿命对象。如果您在对象的构造函数中进行了大量工作,则避免这些工作可以使分配器本身的成本相形见绌,从而使池化更有价值。但是,如果您在对象的构造函数中几乎没有做任何工作,并且对其进行池化,则您正在打赌您的分配器(您的池)对所使用的访问模式比GC更高效,这通常是一个错误的赌注。还存在其他成本,有些情况下,您可能会有效地反对GC的启发式算法;例如,GC基于这样的前提进行优化,即从较高代(例如gen2)对象到较低代(例如gen0)对象的引用相对较少,但是池化对象可能会使这些前提无效。
现在,异步方法创建的对象并不是微小的,而且它们可能出现在超级热的路径上,因此进行池化可能是合理的。但是为了使其尽可能有价值,我们也希望尽可能避免开销。因此,池非常简单,选择使租借和归还非常快速,几乎没有争用,即使这意味着它可能会分配比更积极缓存更多的对象。对于每种状态机类型,实现会为每个线程和每个核心池化多达一个状态机盒子;这使它能够以最小的开销和最小的争用租用和归还(没有其他线程可以同时访问线程特定的缓存,而且很少有其他线程可以同时访问核心特定的缓存)。虽然这可能看起来像是一个相对较小的池,但它也非常有效地显著减少了稳态分配,因为池仅负责存储当前未使用的对象;您可以有一百万个异步方法在任何给定时间都在运行,即使池只能存储每个线程和每个核心一个对象,它仍然可以避免丢失大量对象,因为它只需要存储一个对象足以将其从一个操作传输到另一个操作,而不是在该操作中使用它。
SynchronizationContext and ConfigureAwait