.NET并发编程-函数式组合器

共 8552字,需浏览 18分钟

 ·

2021-04-22 08:45

写给普通:

有问题就会有答案

本系列学习在.NET中的并发并行编程模式,实战技巧

最近两周有点拖更,主要去弄了个大黑耗子。也出现了不少事情,才发现和生活相比,工作真的是最简单快乐的事了。所以就继续聊点开心的,把.NET并发编程啃完。




本小节介绍以函数式的风格来管理异常,特别是异步操作。使用函数组合器常见复杂任务,提高编写并发计算和处理副作用的能力


1、错误处理


在命令式编程中一般直接使用try/catch错误处理,在函数式编程中操作失败不会返回null,而是包装成一个成功或失败的结构表达形式。


1.1 用于错误处理的函数组合器


前面也已经尝试了Retry和Otherwise用于在异常情况下的应用逻辑的异步Task操作。主要是通过利用Task类型的Status和Exception属性来提供错误处理。

 

Image image = await Retry(async () =>  await DownloadImageAsync("Bugghina001.jpg").Otherwise(async () => await DownloadImageAsync("Bugghina002.jpg")),5, TimeSpan.FromSeconds(2));

1.2 Task<Option<T>>处理错误


Option类型强制了开发人员必须编写逻辑来检查值是否存在,从而减轻了null值和error值的许多问题。Option类型的结果要么是Some,要么是None。

 

static async Task<Option<Image>> DownloadOptionImage(string blobReference) {    try    {        var container = await Helpers.GetCloudBlobContainerAsync().ConfigureAwait(false);        CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobReference);        using (var memStream = new MemoryStream())        {            await blockBlob.DownloadToStreamAsync(memStream).ConfigureAwait(false);            return Some(Image.FromStream(memStream));            }    }    catch (Exception)    {        return None;         }}

 

Option类型和Match高阶函数组合使用

 DownloadOptionImage("Bugghina001.jpg").Map(opt => opt.Match(                some: image => image.Save("ImageFolder\Bugghina.jpg"),                none:()=>Log("下载失败")                )); 

 

1.3 使用Result类型保留异常信息


上面使用Task<Option>所有的异常都返回None,没有记录详细的异常信息。Result类型定义如下

   public struct Result<T>    {        public T Ok { get; }                    public Exception Error { get; } 

public bool IsFailed { get => Error != null; } public bool IsOk => !IsFailed;

public Result(T ok) { Ok = ok; Error = default(Exception); } public Result(Exception error) { Error = error; Ok = default(T); }

public R Match<R>(Func<T, R> okMap, Func<Exception, R> failureMap) => IsOk ? okMap(Ok) : failureMap(Error);

public void Match(Action<T> okAction, Action<Exception> errorAction) { if (IsOk) okAction(Ok); else errorAction(Error); }

public static implicit operator Result<T>(T ok) => new Result<T>(ok); // #D public static implicit operator Result<T>(Exception error) => new Result<T>(error); // #D

public static implicit operator Result<T>(Result.Ok<T> ok) => new Result<T>(ok.Value); //#D public static implicit operator Result<T>(Result.Failure error) => new Result<T>(error.Error); }

public static class Result { public struct Ok<L> { internal L Value { get; } internal Ok(L value) { Value = value; }

}

public struct Failure { internal Exception Error { get; } internal Failure(Exception error) { Error = error; } } }

public static class ResultExtensions { public static Result.Ok<T> Ok<T>(T value) => new Result.Ok<T>(value); public static Result.Failure Failure(Exception error) => new Result.Failure(error);

public static async Task<Result<T>> TryCatch<T>(Func<Task<T>> func) { try { return await func(); } catch (Exception ex) { return ex; } }

public static Task<Result<T>> TryCatch<T>(Func<T> func) => TryCatch(() => Task.FromResult(func()));

public static async Task<Result<R>> SelectMany<T, R>(this Task<Result<T>> resultTask, Func<T, Task<Result<R>>> func) { Result<T> result = await resultTask.ConfigureAwait(false); if (result.IsFailed) return result.Error; return await func(result.Ok); }

public static async Task<Result<R>> Select<T, R>(this Task<Result<T>> resultTask, Func<T, Task<R>> func) { Result<T> result = await resultTask.ConfigureAwait(false); if (result.IsFailed) return result.Error; return await func(result.Ok).ConfigureAwait(false); } public static async Task<Result<R>> Match<T, R>(this Task<Result<T>> resultTask, Func<T, Task<R>> actionOk, Func<Exception, Task<R>> actionError) { Result<T> result = await resultTask.ConfigureAwait(false); if (result.IsFailed) return await actionError(result.Error); return await actionOk(result.Ok); }

public static async Task<Result<T>> ToResult<T>(this Task<Option<T>> optionTask) where T : class { Option<T> opt = await optionTask.ConfigureAwait(false);

if (opt.IsSome()) return Ok(opt.Value); return new Exception(); }

public static async Task<Result<R>> OnSuccess<T, R>(this Task<Result<T>> resultTask, Func<T, Task<R>> func) { Result<T> result = await resultTask.ConfigureAwait(false);

if (result.IsFailed) return result.Error; return await func(result.Ok).ConfigureAwait(false); }

public static async Task<Result<T>> OnFailure<T>(this Task<Result<T>> resultTask, Func<Task> func) { Result<T> result = await resultTask.ConfigureAwait(false);

if (result.IsFailed) await func().ConfigureAwait(false); return result; }

public static async Task<Result<R>> Bind<T, R>(this Task<Result<T>> resultTask, Func<T, Task<Result<R>>> func) { Result<T> result = await resultTask.ConfigureAwait(false);

if (result.IsFailed) return result.Error; return await func(result.Ok).ConfigureAwait(false); }

public static async Task<Result<R>> Map<T, R>(this Task<Result<T>> resultTask, Func<T, Task<R>> func) { Result<T> result = await resultTask.ConfigureAwait(false);

if (result.IsFailed) return result.Error;

return await TryCatch(() => func(result.Ok)); }

public static async Task<Result<R>> Match<T, R>(this Task<Result<T>> resultTask, Func<T, R> actionOk, Func<Exception, R> actionError) { Result<T> result = await resultTask.ConfigureAwait(false);

if (result.IsFailed) return actionError(result.Error); return actionOk(result.Ok); }

public static async Task<Result<T>> Ensure<T>(this Task<Result<T>> resultTask, Func<T, Task<bool>> predicate, string errorMessage) { Result<T> result = await resultTask.ConfigureAwait(false);

if (result.IsFailed || !await predicate(result.Ok).ConfigureAwait(false)) return result.Error; return result.Ok; }

public static async Task<Result<T>> Tap<T>(this Task<Result<T>> resultTask, Func<T, Task> action) { Result<T> result = await resultTask.ConfigureAwait(false);

if (result.IsOk) await action(result.Ok).ConfigureAwait(false);

 

使用Result将使代码看起来更易读一些,任务逻辑内部不需要人为的去封装错误返回结果。直接使用Result相同处理。例如下面这样

 

static Result<byte[]> ReadFile(string path){    if(System.IO.File.Exists(path))    {        return System.IO.File.ReadAllBytes(path);    }    else    {        return new FileNotFoundException(path);    }}

 

2、函数式组合器


TPL内置的异步组合器。例如Task.Run,Task.WhenAll和Task.WhenAny等。这些内置组合器可以很容易地扩展来实现有用的组合器以组合和构建更复杂的基于任务的模式。

 

 

 

 

 

 

to be contiued!

下集:使用TPL Dataflow的并行工作流与代理编程?




写给普通:

有问题就会有答案



浏览 36
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报