如何处理TPL数据流中的例外 - 生产者/消费者

时间:2015-09-22 07:04:17

标签: c# exception asynchronous task-parallel-library tpl-dataflow

我有以下测试应用程序模拟我的应用程序场景:

class Program
{
    static void Main()
    {
        Console.WriteLine("***Press R = record to new file, S = Stop Recording, E = Exit");
        var timer = new Timer(Callback, null, 0, 1000);
        while(!_Close) HandleInput(Console.ReadLine());
        Console.WriteLine("Finished");
    }

    private static bool _Close;

    private static void HandleInput(string input)
    {
        switch (input.ToLower())
        {
            case "r": CreateWriter();
                break;
            case "s": Console.WriteLine("File Closed: {0}", _FileWriter.Name); 
                _FileWriter.Dispose();
                _FileWriter = null;
                break;
            case "e":
                _Close = true;
                break;
        }
    }

    private static void CreateWriter()
    {
        if (_FileWriter != null)
            _FileWriter.Dispose();
        string filename = Path.Combine("C:\\", string.Format("{0:yyyy_MM_dd HH_mm_ss}.txt",DateTime.Now));
        _FileWriter = new AsyncFileWriter(filename);
        Console.WriteLine("New File Created: {0}", filename);
    }

    private static void Callback(object state)
    {
        if (_FileWriter != null)
            _FileWriter.Produce(MakeData());
    }

    private static byte[] MakeData()
    {
        string data = string.Empty;
        for (int i = 0; i < 50; i++)
        {
            data += string.Format("{0:yyyy-MM-dd HH:mm:ss.fff}{1}", DateTime.Now, Environment.NewLine);
        }
        return Encoding.UTF8.GetBytes(data);
    }

    private static AsyncFileWriter _FileWriter;
}

它使用以下类作为Consumer(/ producer):

public class AsyncFileWriter : IDisposable
{
    private readonly FileStream _Filewriter;
    private readonly Task _WriteTask;
    private readonly BufferBlock<byte[]> _BufferBlock;

    public AsyncFileWriter(string filename)
    {
        _Filewriter = new FileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None);
        _BufferBlock = new BufferBlock<byte[]>();
        _WriteTask = WriteToFile();
    }

    public void Produce(byte[] data)
    {
        _BufferBlock.Post(data);
    }

    public long Filesize { get; private set; }

    public string Name { get { return _Filewriter.Name; } }

    private async Task WriteToFile()
    {
        while (await _BufferBlock.OutputAvailableAsync())
        {
            byte[] data = _BufferBlock.Receive();
            await _Filewriter.WriteAsync(data, 0, data.Length);
            Filesize = _Filewriter.Length;
        }
    }

    private async Task Complete()
    {
        _BufferBlock.Complete();
        await Task.WhenAll(_WriteTask, _BufferBlock.Completion);
        //now close the file
        _Filewriter.Dispose();
    }

    public void Dispose()
    {
        Complete();
    }
}

需要注意的重要事项是:

  • 我有一个连续的字节流,从一个传递给我 专有库通过回调。我不知道什么时候会收到 这个数据。
  • 我需要能够控制何时开始录制此数据(按“R”)
  • 我需要能够切换到新文件,但确保先前的数据已写入最后一个文件(在录制时再次按“R”)

我有几个问题,因为我对TPL数据流库很陌生。

  1. 首先,我的Producer / Consumer(即AsyncFileWriter)声音的实现是什么?
  2. 我的IDisposable实现好吗?
  3. 如何处理Consumer中的异常(即WriteToFile)?我确实需要在出现问题时收到通知,但正如您所看到的那样,唯一的公共方法是Produce(),并且此处不会发生异常......这种模式是否有最佳实践?

1 个答案:

答案 0 :(得分:1)

Dispose有点破碎,因为它不会等到所有活动都关闭。这可能不是你想要的。请参阅http://blog.stephencleary.com/2013/03/async-oop-6-disposal.html

AsyncFileWriter具有无界缓冲区。如果文件编写器被重载,这将消耗越来越多的内存。

AsyncFileWriter似乎没问题。

但是由于文件系统自己进行缓冲,你可能会完全不使用像AsyncFileWriter这样的东西。只需写入文件就足够了。

关于例外:您可以为此公开事件。或者,如果出现异常,则关闭写入,请记住异常并使类的使用者可以检查异常。此外,生产必须开始失败。处置不应该失败但在处理类的用户之前需要关闭它并检查异常。

因此要么使用事件,要么将类移动到错误状态。

对于消费者而言,错误状态可能稍微好一些,因为异常是在闲暇时被发现的,而不是通过事件推动的。 Complete应公开。调用者应该等待它,并且应该抛出异常。