C# 对于同步和异步lambda,我可以避免类似方法中的重复代码吗?

C# 对于同步和异步lambda,我可以避免类似方法中的重复代码吗?,c#,asynchronous,lambda,async-await,C#,Asynchronous,Lambda,Async Await,我有一个helper方法,它在try/catch/finally块中执行lambda,例如 public void ProcessSpreadsheet(string filename, Action<object[,]> process) { try { // Open MS Excel workbook // Open sheet & extract data into valueArray // ... more

我有一个helper方法,它在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..
    }
}

一切正常。但是我希望避免重复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());
}
公共异步任务进程SpreadsheetAsync(字符串文件名,Func进程){
var asyncTaskComplete=新手动重置事件(错误);
ProcessSpreadsheet(文件名,异步valueArray=>{
等待进程(valueArray);
asyncTaskComplete.Set();
});
等待任务。运行(()=>asyncTaskComplete.WaitOne());
}

但它很混乱,而且没有处理异常。我想知道是否还有别的办法?

我会放弃接受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..
    }
}
public void ProcessSpreadsheet(string filename, Action<object[,]> process)
{
  ProcessSpreadsheetAsync(filename, async data => { process(data); }).GetAwaiter().GetResult();
}
如果同步代码正在使用它,它只需执行以下操作:

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) {
    // ...
}
他们所有的错误处理方式都完全相同

我已经阅读并同意Stephen Toub关于async over sync的帖子,但这并不适用于这里,因为该方法的关键在于,异步是有益还是有害,是由消费者通过lambda提供的

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

就解决方案而言,首先想到的是只接受异步lambda。这在概念上类似于在接口中定义
任务
-返回方法-实现可能是异步的,也可能是同步的

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

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..
  }
}
在这种情况下,您还可以编写如下包装:

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..
    }
}
public void ProcessSpreadsheet(string filename, Action<object[,]> process)
{
  ProcessSpreadsheetAsync(filename, async data => { process(data); }).GetAwaiter().GetResult();
}
public void ProcessSpreadsheet(字符串文件名,操作流程)
{
ProcessSpreadsheetAsync(文件名,异步数据=>{process(data);}).GetAwaiter().GetResult();
}

但是,这只是安全的,因为
ProcessSpreadsheetAsync
只有一个
wait
,它位于其
进程上。如果将
ProcessSpreadsheetAsync
更改为另一个
等待
,则包装器很容易导致死锁。

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

您试图避免重复哪些代码?try/catch/finally代码?为什么要通过
任务运行代码。在异步版本中运行
?使用异步版本,你到底想实现什么?@YacoubMassad是的,应该是Func,我已经解决了,谢谢。这段代码真的有效吗?
process
抛出的异常是否会被catch块捕获?是的,我希望避免重复try/catch/finally代码。对于传入的lambda将从异步中受益的情况,我想要一个异步版本。例如,我使用同步版本来计算电子表格中的总计,但我希望使用异步版本,在异步版本中,我对电子表格中的每一行进行网络调用。这会很慢,我想让UI保持响应,所以我想在异步lambda中执行。@YacoubMassad,不,你又是对的,没有捕获异常:(.我想我需要比我想象的更多的帮助。在这里复制代码不是比强制同步方法异步运行更好吗?因为这会增加堆上额外分配的成本。@NazmiAltun:同步方法仍然同步运行;它们返回一个已完成的任务。如果你想避免分配,你可以为
任务定义一个
静态
字段。从结果(true)
或使用
值任务
public void ProcessSpreadsheet(string filename, Action<object[,]> process)
{
  ProcessSpreadsheetAsync(filename, async data => { process(data); }).GetAwaiter().GetResult();
}