添加VS项目

main
wanggaofeng 9 months ago
parent 70423a49fd
commit f145e993e3

@ -0,0 +1,24 @@
global using System.IO;
global using System.Collections;
global using System.Collections.Concurrent;
global using System.Collections.Generic;
global using System.Collections.Immutable;
global using System.Collections.Specialized;
global using System.Configuration;
global using System.Diagnostics;
global using System.Dynamic;
global using System.Threading;
global using System.Threading.Tasks;
global using Polly;
global using Polly.CircuitBreaker;
global using Polly.Fallback;
global using Polly.Retry;
global using Polly.Timeout;
global using Polly.Wrap;
global using Polly.Extensions.Http;
global using Polly.Bulkhead;
global using Polly8Study.Core;

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Polly8Study.Core
{
public class HttpClientUtil
{
}
}

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.7" />
<PackageReference Include="Polly" Version="8.4.1" />
<PackageReference Include="Polly.Extensions" Version="8.4.1" />
</ItemGroup>
</Project>

@ -0,0 +1,29 @@
global using System.IO;
global using System.Collections;
global using System.Collections.Concurrent;
global using System.Collections.Generic;
global using System.Collections.Immutable;
global using System.Collections.Specialized;
global using System.Configuration;
global using System.Diagnostics;
global using System.Dynamic;
global using System.Threading;
global using System.Threading.Tasks;
global using Xunit;
global using Xunit.Sdk;
global using Xunit.Extensions;
global using Xunit.Abstractions;
global using Polly;
global using Polly.CircuitBreaker;
global using Polly.Fallback;
global using Polly.Retry;
global using Polly.Timeout;
global using Polly.Wrap;
global using Polly.Extensions.Http;
global using Polly.Bulkhead;
global using Polly8Study.Core;

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Polly8Study.Core\Polly8Study.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>

@ -0,0 +1,495 @@
namespace Polly8Study.Test
{
/// <summary>
/// Polly8超时策略 测试
/// 关键:
/// CancellationToken没有这个是不起使用的
/// 就是之前版本中的乐观超时,悲观超时貌似取消了
/// </summary>
public class Polly8TimeoutTest
{
private readonly ITestOutputHelper _output;
public Polly8TimeoutTest(ITestOutputHelper testOutput)
{
_output = testOutput;
}
/// <summary>
/// 超时策略选项
/// </summary>
[Fact]
public void TimeoutStrategyOptions_Test()
{
//直接设置
new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromMilliseconds(100))
.Build();
//默认值
var defaultOption = new TimeoutStrategyOptions();
//全功能
TimeoutStrategyOptions timeoutOption = new TimeoutStrategyOptions
{
//名称唯一标识了特定策略的特定实例,也包含在由各个弹性策略生成的遥测数据中
Name = "timeout",
//固定超时时间(启用了TimeoutGenerator则无效)
Timeout = TimeSpan.FromSeconds(2),
//动态超时时间设置
//TimeoutGenerator = arg =>
//{
// var ts = TimeSpan.FromSeconds(Math.Pow(2, 1));
// return ValueTask.FromResult(ts);
//},
//发生超时时引发的超时委托
OnTimeout = (args) =>
{
var key = args.Context.OperationKey;
_output.WriteLine("OnTimeout");
return ValueTask.CompletedTask;
},
};
//使用
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddTimeout(timeoutOption)
.Build();
}
/// <summary>
/// 非可取消任务:忽略超时
/// 同步执行
/// </summary>
[Fact]
public void No_CancellationToken_Test()
{
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(1))
.Build();
//外部tcs可省略
var tcs = new CancellationTokenSource();
try
{
pipeline.Execute
(
//此处必须是可取消的任务,否则超时不作为(一直等待)
callback: (CancellationToken innerToken) =>
{
//虽然接口耗时大,因为没有使用‘可取消的’ Send请求超时设置被忽略会等待接口正确返回
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost:44344/api/Timeout/Slow");
//关键Send方法 没有传入参数 cancellationToken
//其它同步方法,只要是可取消方法均可
HttpResponseMessage response = new HttpClient().Send(requestMessage);
if (response.IsSuccessStatusCode == false)
{
_output.WriteLine($"非正常响应,响应码:{response.StatusCode}");
return;
}
var stream = response.Content.ReadAsStream();
var bs = new byte[stream.Length];
stream.Read(bs, 0, (int)stream.Length);
var text = System.Text.UTF8Encoding.UTF8.GetString(bs);
_output.WriteLine($"响应内容:{text}");
_output.WriteLine("任务执行完成");
},
cancellationToken: tcs.Token
);
}
catch (OperationCanceledException ex)
{
_output.WriteLine($"任务取消,引发异常:{ex.Message}");
}
catch (TimeoutRejectedException ex)
{
_output.WriteLine($"超时,引发异常:{ex.Message}");
}
catch (Exception ex)
{
_output.WriteLine($"API服务异常{ex.Message}");
}
finally
{
tcs.TryReset();
}
}
/// <summary>
/// 可取消任务:超时
/// 同步执行
/// </summary>
[Fact]
public void Has_CancellationToken_Timeout_Test()
{
TimeoutStrategyOptions timeoutOption = new TimeoutStrategyOptions
{
//名称唯一标识了特定策略的特定实例,也包含在由各个弹性策略生成的遥测数据中
Name = "timeout",
//固定超时时间(启用了TimeoutGenerator则无效)
Timeout = TimeSpan.FromSeconds(1),
//动态超时时间设置
//TimeoutGenerator = arg =>
//{
// var ts = TimeSpan.FromSeconds(Math.Pow(2, 1));
// return ValueTask.FromResult(ts);
//},
//发生超时时引发的超时委托
OnTimeout = (args) =>
{
_output.WriteLine("OnTimeout 超时委托执行");
return ValueTask.CompletedTask;
},
};
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddTimeout(timeoutOption)
.Build();
var tcs = new CancellationTokenSource();
try
{
pipeline.Execute
(
//此处必须是可取消的任务,否则超时不作为(一直等待)
callback: (innerToken) =>
{
HttpRequestMessage r = new HttpRequestMessage(HttpMethod.Get, "http://localhost:44344/api/Timeout/Slow");
var response = new HttpClient().Send(r, innerToken);
if (response.IsSuccessStatusCode == false)
{
Console.WriteLine("非正常响应");
return;
}
var stream = response.Content.ReadAsStream();
var bs = new byte[stream.Length];
stream.Read(bs, 0, (int)stream.Length);
var text = System.Text.UTF8Encoding.UTF8.GetString(bs);
_output.WriteLine($"响应内容:{text}");
_output.WriteLine("任务执行完成");
},
cancellationToken: tcs.Token
);
}
catch (OperationCanceledException ex)
{
_output.WriteLine($"任务取消,引发异常:{ex.Message}");
}
catch (TimeoutRejectedException ex)
{
_output.WriteLine($"超时,引发异常:{ex.Message}");
}
catch (Exception ex)
{
_output.WriteLine($"API服务异常{ex.Message}");
}
finally
{
tcs.TryReset();
}
}
/// <summary>
/// 可取消任务:不超时
/// 同步执行
/// </summary>
[Fact]
public void Has_CancellationToken_NotTimeout_Test()
{
TimeoutStrategyOptions timeoutOption = new TimeoutStrategyOptions
{
//名称唯一标识了特定策略的特定实例,也包含在由各个弹性策略生成的遥测数据中
Name = "timeout",
//固定超时时间(启用了TimeoutGenerator则无效)
Timeout = TimeSpan.FromSeconds(5),
//动态超时时间设置
//TimeoutGenerator = arg =>
//{
// var ts = TimeSpan.FromSeconds(Math.Pow(2, 1));
// return ValueTask.FromResult(ts);
//},
//发生超时时引发的超时委托
OnTimeout = (args) =>
{
Console.WriteLine("OnTimeout 超时委托执行");
return ValueTask.CompletedTask;
},
};
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddTimeout(timeoutOption)
.Build();
var tcs = new CancellationTokenSource();
try
{
pipeline.Execute
(
//此处必须是可取消的任务,否则超时不作为(一直等待)
callback: (innerToken) =>
{
HttpRequestMessage r = new HttpRequestMessage(HttpMethod.Get, "http://localhost:44344/api/Timeout/Slow");
var response = new HttpClient().Send(r, innerToken);
if (response.IsSuccessStatusCode == false)
{
Console.WriteLine("非正常响应");
return;
}
var stream = response.Content.ReadAsStream();
var bs = new byte[stream.Length];
stream.Read(bs, 0, (int)stream.Length);
var text = System.Text.UTF8Encoding.UTF8.GetString(bs);
_output.WriteLine($"响应内容:{text}");
_output.WriteLine("任务执行完成");
},
cancellationToken: tcs.Token
);
}
catch (OperationCanceledException ex)
{
_output.WriteLine($"任务取消,引发异常:{ex.Message}");
}
catch (TimeoutRejectedException ex)
{
_output.WriteLine($"超时,引发异常:{ex.Message}");
}
catch (Exception ex)
{
_output.WriteLine($"API服务异常{ex.Message}");
}
finally
{
tcs.TryReset();
}
}
/// <summary>
/// 不可取消任务:忽略超时
/// 异步执行
/// </summary>
[Fact]
public async void No_CancellationToken_ExecuteAsync_Test()
{
TimeoutStrategyOptions timeoutOption = new TimeoutStrategyOptions
{
//名称唯一标识了特定策略的特定实例,也包含在由各个弹性策略生成的遥测数据中
Name = "timeout",
//固定超时时间(启用了TimeoutGenerator则无效)
Timeout = TimeSpan.FromSeconds(1),
//发生超时时引发的超时委托
OnTimeout = (args) =>
{
_output.WriteLine("OnTimeout 超时委托执行");
return ValueTask.CompletedTask;
},
};
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddTimeout(timeoutOption)
.Build();
var tcs = new CancellationTokenSource();
try
{
await pipeline.ExecuteAsync
(
//此处为不可取消的任务,忽略超时(会一直等待)
callback: async (innerToken) =>
{
//完成大约是2秒多而不是超时的1秒多
await Task.Delay (1000*3);
_output.WriteLine("任务执行完成");
},
cancellationToken: tcs.Token
);
}
catch (OperationCanceledException ex)
{
_output.WriteLine($"任务取消,引发异常:{ex.Message}");
}
catch (TimeoutRejectedException ex)
{
_output.WriteLine($"超时,引发异常:{ex.Message}");
}
catch (Exception ex)
{
_output.WriteLine($"API服务异常{ex.Message}");
}
finally
{
tcs.TryReset();
}
}
/// <summary>
/// 可取消任务:超时
/// 异步执行
/// </summary>
/// <returns></returns>
[Fact]
public async Task Has_CancellationToken_ExecuteAsync_Timeout_Test()
{
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(1))
.Build();
await Assert.ThrowsAnyAsync<Exception>(async () =>
{
var cts = new CancellationTokenSource();
await pipeline.ExecuteAsync
(
callback: async (innerToken) =>
{
//完成大约是超时的1秒多而不是任务的3秒多
await Task.Delay(1000 * 3, innerToken);
_output.WriteLine("任务执行完成");
},
cancellationToken: cts.Token
);
});
}
/// <summary>
/// 可取消任务:不超时
/// 异步执行
/// </summary>
/// <returns></returns>
[Fact]
public async Task Has_CancellationToken_ExecuteAsync_NoTimeout_Test()
{
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(3))
.Build();
try
{
var cts = new CancellationTokenSource();
await pipeline.ExecuteAsync
(
callback: async (innerToken) =>
{
//不超时完成大约是任务耗时的1秒
await Task.Delay(1000, innerToken);
_output.WriteLine("任务执行完成");
},
cancellationToken: cts.Token
);
}
catch (Exception ex)
{
_output.WriteLine(ex.Message);
}
}
// <summary>
/// 可取消任务:超时
/// 异步执行无外层Token
/// </summary>
/// <returns></returns>
[Fact]
public async Task Has_OuterCancellationToken_ExecuteAsync_Timeout_Test()
{
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(1))
.Build();
await Assert.ThrowsAnyAsync<Exception>(async () =>
{
await pipeline.ExecuteAsync
(
callback: async (innerToken) =>
{
//完成大约是超时的1秒多而不是任务的3秒多
await Task.Delay(1000 * 3, innerToken);
_output.WriteLine("任务执行完成");
}
);
});
}
[Fact]
public void SyncWithCancellationToken_Test()
{
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(3))
.Build();
var tcs = new CancellationTokenSource();
try
{
pipeline.Execute
(
//此处必须是可取消的任务,否则超时不作为(一直等待)
callback: SyncWithCancellationToken,
cancellationToken: tcs.Token
);
}
catch (OperationCanceledException ex)
{
_output.WriteLine($"任务取消,引发异常:{ex.Message}");
}
catch (TimeoutRejectedException ex)
{
_output.WriteLine($"超时,引发异常:{ex.Message}");
}
catch (Exception ex)
{
_output.WriteLine($"API服务异常{ex.Message}");
}
finally
{
tcs.TryReset();
}
}
/// <summary>
/// 使用CancellationToken的同步方法
/// </summary>
private void SyncWithCancellationToken(CancellationToken cancellationToken)
{
Thread thread = new Thread((token) =>
{
int max = 500;
while (max > 0)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
max -= 1;
Thread.Sleep(10);
}
});
thread.Start();
thread.Join();
}
}
}

@ -0,0 +1,21 @@
namespace Polly8Study.Test
{
public class UseXunit
{
private readonly ITestOutputHelper _output;
public UseXunit(ITestOutputHelper testOutput)
{
_output = testOutput;
}
[Fact]
public void Use_xUnit_Test()
{
var msg = "使用 xUnit 单元测试框架!";
Assert.True(true, msg);
_output.WriteLine(msg);
}
}
}

@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Polly8Study.WebApi.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class TimeoutController : ControllerBase
{
private readonly ILogger<TimeoutController> _logger;
public TimeoutController(ILogger<TimeoutController> logger)
{
_logger = logger;
}
/// <summary>
/// 快速接口
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Fast()
{
return Ok("Fast");
}
/// <summary>
/// 慢接口耗时约2秒
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> Slow(CancellationToken token)
{
//使用“异常过滤器”, 全局统一处理
try
{
await Task.Delay(1000*2, token);
return Ok("Slow");
}
catch (OperationCanceledException ex)
{
Console.WriteLine("用户取消操作:" + ex.Message);
throw;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return Problem("服务器异常");
}
finally
{
Console.WriteLine("请求结束");
}
}
}
}

@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Polly8Study.WebApi.Filters
{
/// <summary>
/// 用户取消调用(刷新浏览器等)异常过滤器
/// </summary>
public class OperationCancelledExceptionFilter : IAsyncExceptionFilter
{
public Task OnExceptionAsync(ExceptionContext context)
{
if (context.Exception is OperationCanceledException ex)
{
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
//context.ActionDescriptor.ContrallerName;
Console.WriteLine($"全局异常过滤器:{controllerActionDescriptor?.ControllerName}{controllerActionDescriptor?.ActionName} 请求,被用户取消!");
}
return Task.CompletedTask;
}
}
}

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
</Project>

@ -0,0 +1,6 @@
@Polly8Study.WebApi_HostAddress = http://localhost:5000
GET {{Polly8Study.WebApi_HostAddress}}/weatherforecast/
Accept: application/json
###

@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Polly8Study.WebApi.Filters;
namespace Polly8Study.WebApi
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers(option =>
{
option.Filters.Add<OperationCancelledExceptionFilter>();
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//builder.Services.AddSingleton<IAsyncExceptionFilter>(new OperationCancelledExceptionFilter);
builder.Services.Configure<MvcOptions>(mvcOptions =>
{
mvcOptions.Filters.Add<OperationCancelledExceptionFilter>();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}

@ -0,0 +1,31 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:46344",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:44344",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Information"
}
}
}

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Information"
}
},
"AllowedHosts": "*"
}

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35027.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly8Study.Test", "Polly8Study.Test\Polly8Study.Test.csproj", "{6BA907CA-61B6-4956-A1FE-9F7596BC10B9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly8Study.Core", "Polly8Study.Core\Polly8Study.Core.csproj", "{F8A1BC0D-978F-42A8-8747-5C8F635060C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Polly8Study.WebApi", "Polly8Study.WebApi\Polly8Study.WebApi.csproj", "{AEE3A7E0-DAD3-46ED-BF34-9D31A60CEFFD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6BA907CA-61B6-4956-A1FE-9F7596BC10B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BA907CA-61B6-4956-A1FE-9F7596BC10B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BA907CA-61B6-4956-A1FE-9F7596BC10B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BA907CA-61B6-4956-A1FE-9F7596BC10B9}.Release|Any CPU.Build.0 = Release|Any CPU
{F8A1BC0D-978F-42A8-8747-5C8F635060C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8A1BC0D-978F-42A8-8747-5C8F635060C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8A1BC0D-978F-42A8-8747-5C8F635060C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8A1BC0D-978F-42A8-8747-5C8F635060C3}.Release|Any CPU.Build.0 = Release|Any CPU
{AEE3A7E0-DAD3-46ED-BF34-9D31A60CEFFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEE3A7E0-DAD3-46ED-BF34-9D31A60CEFFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEE3A7E0-DAD3-46ED-BF34-9D31A60CEFFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEE3A7E0-DAD3-46ED-BF34-9D31A60CEFFD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0B24E74B-1EE9-4EEE-B199-8CB3018FA4D5}
EndGlobalSection
EndGlobal
Loading…
Cancel
Save