我可以在同步和异步lambdas的类似方法中避免重复代码吗?

时间:2016-05-02 21:35:29

标签: c# asynchronous lambda async-await

我有一个帮助方法,它在一个try / catch / finally块中执行lambda,例如。

public void ProcessSpreadsheet(string filename, Action<object[,]> process) {
    try {
        // Open MS Excel workbook
        // Open sheet & extract data into valueArray
        // ... more boiler plate ...

        process(valueArray);
    }
    catch (FooException e) {
        LogFoo(e.Message);
        throw;
    }
    catch (BarException e) {
        LogBar(e.Message);
        throw;
    }
    finally {
        // Close workbook, release resources, etc..
    }
}

我现在想创建一个接受异步lambda的异步版本。

(注意,我已经阅读并同意Stephen Toub的post s关于 async over sync ,但这并不是在这里应用作为方法的关键,其中异步是或不是有益的,由消费者通过lambda提供。)

我可以复制并粘贴以上内容,将其标记为async,将返回类型更改为Task,添加&#34; Async&#34;根据它的名称,将process参数的类型更改为Func<object[,],Task>并更改

process(valueArray);

await process(valueArray);

一切正常。但是我想避免重复try / catch / finally中的所有代码。如果没有重复的代码,是否有一种干净的方法来实现这一目标?

到目前为止,我得到的最佳解决方案是:

public async Task ProcessSpreadsheetAsync(string filename, Func<object[,],Task> process) {
    var asyncTaskComplete = new ManualResetEvent(false);
    ProcessSpreadsheet(filename, async valueArray => {
        await process(valueArray);
        asyncTaskComplete.Set();
    });
    await Task.Run(() => asyncTaskComplete.WaitOne());
}

但它很乱,但例外情况并未得到处理。我想知道是否有另一种方式?

3 个答案:

答案 0 :(得分:1)

  

我已阅读并同意Stephen Toub关于异步过度同步的帖子,但这并不适用于此方法的关键,其中异步是或不是有益的,由消费者通过lambda提供。

嗯,是的,不是。我可以看到你说消费代码将决定它是否是异步的。但为了避免重复,您仍然必须注意同步异步和异步同步反模式。

就解决方案而言,首先想到的是接受异步lambdas。这在概念上类似于在接口中定义Task - 返回方法 - 实现可以是异步的,或者它也可以是同步的。

在您的情况下,它看起来像:

public async Task ProcessSpreadsheetAsync(string filename, Func<object[,],Task> process) {
  try {
    // Open MS Excel workbook
    // Open sheet & extract data into valueArray
    // ... more boiler plate ...

    await process(valueArray);
  }
  catch (FooException e) {
    LogFoo(e.Message);
    throw;
  }
  catch (BarException e) {
    LogBar(e.Message);
    throw;
  }
  finally {
    // Close workbook, release resources, etc..
  }
}

我会留下它。任何同步过程都可以:

await ProcessSpreadsheetAsync(filename, data => { ...; return Task.FromResult(true); });

在这个特定的情况下,您还可以编写这样的包装器:

public void ProcessSpreadsheet(string filename, Action<object[,]> process)
{
  ProcessSpreadsheetAsync(filename, async data => { process(data); }).GetAwaiter().GetResult();
}

但是,是安全的,因为ProcessSpreadsheetAsync只有一个await,位于process。如果ProcessSpreadsheetAsync被更改为另一个await,则包装器很容易导致死锁。

答案 1 :(得分:1)

要回答您的实际问题而不是告诉您做其他事情,只需将try / catch块提取到函数中即可。这通常被建议作为关注点分离的一部分。乱码逻辑与错误处理是不好的形式。

答案 2 :(得分:0)

我会放弃接受lambda的想法。这是在您的代码中承担真正不属于的责任。相反,只需提供一个如下所示的重载:

public object[,] ProcessSpreadsheet(string filename) {
    try {
        // Open MS Excel workbook
        // Open sheet & extract data into valueArray
        // ... more boiler plate ...

        return valueArray;
    }
    catch (FooException e) {
        LogFoo(e.Message);
        throw;
    }
    catch (BarException e) {
        LogBar(e.Message);
        throw;
    }
    finally {
        // Close workbook, release resources, etc..
    }
}

如果同步代码正在使用它,它只会执行:

try {
    var results = ProcessSpreadsheet("...");
    DoSomethingElse(results);
} catch (FooException e) {
    // ...
} catch (CanHappenWhileDoingSomethingElseException e) {
    // ...
}

就处理异常而言,代码的使用者具有无限的灵活性。

如果有人使用async / await,他们所要做的就是:

try {
    var results = await Task.Run(() => ProcessSpreadsheet("..."));
    DoSomethingElse(results);
} catch (FooException e) {
    // ...
} catch (CanHappenWhileDoingSomethingElseException e) {
    // ...
}

他们所有的错误处理方式完全相同。