防止重叠的异步代码执行

时间:2016-05-26 13:27:59

标签: c# async-await

我已经读过有关async-await模式的信息,并且Mutex与异步代码不兼容,所以我想知道如何以轻量级的方式编写以下方法,没有对象分配(甚至不是Task<> )但没有过多的责任(来自模式)。

问题如下。看看这个方法:

    public async void SendAsync(Stream stream, byte[] data)
    {
        await stream.WriteAsync(data.Length);
        await stream.WriteAsync(data);
        await stream.FlushAsync();
    }

现在,这种方法可能不是很有用。事实上,有更多的代码,但对于手头的问题,它是完全完整的。

这里的问题是我有一个主线程,可以在没有await的情况下快速调用该方法:

for (i = 0; i < 10; i++)
{
    SendAsync(stream[i], buffer[i]);
}

我想要的只是主线程快速循环而不会阻塞。

现在,这个循环也循环了:

while (true)
{
    for (i = 0; i < 10; i++)
    {
        SendAsync(stream[i], buffer[i]);
    }

    // TODO: add a break criterion here
}

我怀疑(恐惧)是在拥挤的buffer(是的,它下面的TCP / IP stream[i])上有一个非常厚实的NetworkStream(比如1 MB)导致n th 调用在前面的n-1 th 调用可重入(从调用返回)之前发生

这两项任务将相互干扰。

我不想要我已经丢弃的这两种解决方案中的任何一种:

  1. (非)解决方案1:Mutex
  2. 这将引发异常,因为Mutex具有线程关联性,而异步代码与线程无关:

        public async void SendAsync(Stream stream, byte[] data)
        {
            mutex.WaitOne(); // EXCEPTION!
    
            await stream.WriteAsync(data.Length);
            await stream.WriteAsync(data);
            await stream.FlushAsync();
    
            mutex.ReleaseMutex();
        }
    
    1. 解决方案2:将Task返回*.WaitAll()
    2. 循环在消费者的土地上,而SendAsync()在框架土地上,我不想强​​迫消费者使用错综复杂的模式:

      while (true)
      {
          for (i = 0; i < 10; i++)
          {
              tasks[i] = SendAsync(stream[i], buffer[i]);
          }
      
          Task.WaitAll(tasks);
      
          // TODO: add a break criterion here
      }
      

      此外,我绝对不希望在每个循环上分配Task,此代码将以中等频率(5-10 Hz)运行,具有1000多个开放流。我不能每秒分配10000多个对象,它会像GC一样疯狂。请注意,这是每个SO策略的最小代码,实际代码要复杂得多。

      我希望看到的是某种外循环分配,只有在绝对必要时才会等待完成(例如Mutex)但是同一时间不会在循环内部分配内存。

1 个答案:

答案 0 :(得分:2)

假设你有一个关于流的包装类,你可以这样做:

private Stream stream;
private Task currentTask;

public async void SendAsync(byte[] data)
{
    currentTask = WriteAsyncWhenStreamAvailable(data);
    await currentTask;
}

private async Task WriteAsyncWhenStreamAvailable(byte[] data)
{
    if (currentTask != null)
        await currentTask.ConfigureAwait(false);
    await WriteAsync(data);
}

public async Task WriteAsync(byte[] data)
{
    await stream.WriteAsync(data.Length).ConfigureAwait(false);
    await stream.WriteAsync(data).ConfigureAwait(false);
    await stream.FlushAsync().ConfigureAwait(false);
}

这样,您始终可以在发送新数据之前等待之前的WriteAsync结束。此解决方案将确保以请求的顺序发送缓冲区。

您可以使用async SemaphoreSlim代替Mutex

private SemaphoreSlim semaphore = new SemaphoreSlim(1);

public async void SendAsync(Stream stream, byte[] data)
{
    await semaphore.WaitAsync(); // EXCEPTION!

    await stream.WriteAsync(data.Length);
    await stream.WriteAsync(data);
    await stream.FlushAsync();

    semaphore.Release();
}

请注意OutOfMemoryException如果您在不考虑待处理缓冲区大小的情况下无盲地生成新数据...