我有一个帮助方法,它在一个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());
}
但它很乱,但例外情况并未得到处理。我想知道是否有另一种方式?
答案 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) {
// ...
}
他们所有的错误处理方式完全相同。