├── .gitattributes
├── .github
└── workflows
│ ├── dotnet-build.yml
│ └── nuget-tag-publish.yml
├── .gitignore
├── CHANGELOG.md
├── Directory.Build.props
├── LICENSE
├── LightWorkFlowManager.sln
├── README.md
├── build
└── Version.props
└── src
├── LightWorkFlowManager.Tests
├── LightWorkFlowManager.Tests.csproj
├── MessageWorkerManagerTest.cs
└── MessageWorkerTest.cs
└── LightWorkFlowManager
├── Contexts
├── IWorkerContext.cs
├── WorkFlowErrorCode.cs
└── WorkerContext.cs
├── Exceptions
├── IWorkFlowException.cs
├── MessageWorkerException.cs
├── MessageWorkerInputArgumentException.cs
├── MessageWorkerInputNotFoundException.cs
├── WorkFlowException.cs
└── WorkerContextNotFoundException.cs
├── LightWorkFlowManager.csproj
├── MessageWorkerManager.cs
├── Monitors
└── IWorkerRunMonitor.cs
├── Protocols
├── MessageWorkerStatus.cs
└── WorkerResult.cs
└── Workers
├── DelegateMessageWorker.cs
├── IMessageWorker.cs
├── IMessageWorkerManagerSensitive.cs
├── MessageWorker.cs
└── MessageWorkerBase.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-build.yml:
--------------------------------------------------------------------------------
1 | name: .NET Build
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: windows-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 |
13 | - name: Build with dotnet
14 | run: dotnet build --configuration Release
15 |
16 | - name: Test
17 | run: dotnet test --configuration Release
--------------------------------------------------------------------------------
/.github/workflows/nuget-tag-publish.yml:
--------------------------------------------------------------------------------
1 | name: publish nuget
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: windows-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v1
15 |
16 | - name: Install dotnet tool
17 | run: dotnet tool install -g dotnetCampus.TagToVersion
18 |
19 | - name: Set tag to version
20 | run: dotnet TagToVersion -t ${{ github.ref }}
21 |
22 | - name: Build with dotnet
23 | run: |
24 | dotnet build --configuration Release
25 | dotnet pack --configuration Release --no-build
26 |
27 | - name: Install Nuget
28 | uses: nuget/setup-nuget@v1
29 | with:
30 | nuget-version: '5.x'
31 |
32 | - name: Add private GitHub registry to NuGet
33 | run: |
34 | nuget sources add -name github -Source https://nuget.pkg.github.com/dotnet-campus/index.json -Username dotnet-campus -Password ${{ secrets.GITHUB_TOKEN }}
35 |
36 | - name: Push generated package to GitHub registry
37 | run: |
38 | nuget push .\bin\Release\*.nupkg -Source github -SkipDuplicate
39 | nuget push .\bin\Release\*.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -ApiKey ${{ secrets.NugetKey }}
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # LightWorkFlowManager
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | latest
5 | $(MSBuildThisFileDirectory)bin\$(Configuration)
6 | dotnet campus(.NET 职业技术学院)
7 |
8 | dotnet-campus
9 | https://github.com/dotnet-campus/LightWorkFlowManager
10 | https://github.com/dotnet-campus/LightWorkFlowManager
11 | 轻量的工作过程管理
12 |
13 | git
14 | MIT
15 | Copyright © 2023 dotnet campus, All Rights Reserved.
16 |
17 | README.md
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 dotnet campus
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LightWorkFlowManager.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.6.33723.286
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5D196596-756D-45C2-8A05-C8E4AB8A36E6}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LightWorkFlowManager", "src\LightWorkFlowManager\LightWorkFlowManager.csproj", "{F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LightWorkFlowManager.Tests", "src\LightWorkFlowManager.Tests\LightWorkFlowManager.Tests.csproj", "{0B617CB5-1D30-47FC-B914-066A6D5B1D63}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {0B617CB5-1D30-47FC-B914-066A6D5B1D63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {0B617CB5-1D30-47FC-B914-066A6D5B1D63}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {0B617CB5-1D30-47FC-B914-066A6D5B1D63}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {0B617CB5-1D30-47FC-B914-066A6D5B1D63}.Release|Any CPU.Build.0 = Release|Any CPU
26 | EndGlobalSection
27 | GlobalSection(SolutionProperties) = preSolution
28 | HideSolutionNode = FALSE
29 | EndGlobalSection
30 | GlobalSection(NestedProjects) = preSolution
31 | {F7ED61F4-920C-49EB-8DC1-74B2BE6AF272} = {5D196596-756D-45C2-8A05-C8E4AB8A36E6}
32 | {0B617CB5-1D30-47FC-B914-066A6D5B1D63} = {5D196596-756D-45C2-8A05-C8E4AB8A36E6}
33 | EndGlobalSection
34 | EndGlobal
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LightWorkFlowManager
2 |
3 | 轻量的工作过程管理
4 |
5 | [](https://www.nuget.org/packages/dotnetCampus.LightWorkFlowManager)
6 | [](https://github.com/dotnet-campus/LightWorkFlowManager/issues)
7 | [](https://github.com/dotnet-campus/LightWorkFlowManager/network/members)
8 | [](https://github.com/dotnet-campus/LightWorkFlowManager/stargazers)
9 | [](https://github.com/dotnet-campus/LightWorkFlowManager/)
10 | [](https://github.com/dotnet-campus/LightWorkFlowManager/)
11 |
12 | 世界上有许多现有的工作过程管理,那为什么还重新造了一个?没啥原因,自己造的用的顺手
13 |
14 | 为了不和旧代码冲突,这里命名 Worker 为 MessageWorker 类型
15 |
16 | ## 使用方法
17 |
18 | 1、 创建 MessageWorkerManager 对象
19 |
20 | ```csharp
21 | // 每个任务一个 TaskId 号
22 | string taskId = Guid.NewGuid().ToString();
23 | // 相同类型的任务采用相同的名字,比如这是做 PPT 解析的任务
24 | string taskName = "PPT 解析";
25 | // 提供容器
26 | IServiceScope serviceScope = serviceProvider.CreateScope();
27 |
28 | var workerManager = new MessageWorkerManager(taskId, taskName, serviceScope);
29 | ```
30 |
31 | 2、 定义 Worker 工作器
32 |
33 | ```csharp
34 | record InputType();
35 |
36 | record OutputType();
37 |
38 | class FooWorker : MessageWorker
39 | {
40 | protected override ValueTask> DoInnerAsync(InputType input)
41 | {
42 | ...
43 | }
44 | }
45 | ```
46 |
47 | 每个工作器都可以声明其输入和输出类型,输入类型将会自动由框架注入,输出类型将会自动存储到框架里面
48 |
49 | 3、 执行 Worker 工作器
50 |
51 | ```csharp
52 | var result = await workerManager
53 | .GetWorker()
54 | .RunAsync();
55 | ```
56 |
57 | 以上代码也可以简写为以下代码
58 |
59 | ```csharp
60 | var result = await workerManager.RunWorker();
61 | ```
62 |
63 | ## 机制和功能
64 |
65 | ### 工作器参数
66 |
67 | 工作器参数通过 MessageWorkerManager 工作器管理的 IWorkerContext 上下文读取。每个工作器的可返回值类型都会自动被设置到 IWorkerContext 上下文里面。如此即可自动实现上一个 Worker 的输出作为下一个 Worker 的输入
68 |
69 | 在每个工作器里面,都可以通过 `SetContext` 设置上下文信息
70 |
71 | 在开始执行工作器时,还可以手动设置输入参数,如以下例子
72 |
73 | ```csharp
74 | // 例子1:先获取工作器,再赋值给到工作器的执行方法
75 |
76 | await Manager
77 | .GetWorker()
78 | .RunAsync(new InputType());
79 |
80 | // 例子2: 通过 SetContext 进行设置参数,再执行工作器
81 | await Manager
82 | .SetContext(new InputType())
83 | .RunWorker();
84 | ```
85 |
86 | 如果有些工作器之间的输入和输出参数需要进行转换,也可以 `SetContext` 传入转换委托进行参数转换
87 |
88 | ```csharp
89 | // 以下例子将当前上下文里的 Foo1Type 类型转换为 FooWorker 需要的 Foo2Type 参数
90 | await Manager
91 | .SetContext((Foo1Type foo1) => ConvertFoo1ToFoo2(foo1))
92 | .RunWorker();
93 | ```
94 |
95 | ### 异常中断和重试
96 |
97 | 每个 Worker 都可以返回 WorkerResult 类型的返回值,可以在返回值里面告知框架层是否当前的 Worker 执行成功。在执行失败时,可以赋值错误码,方便定位调试或输出。在执行失败时,可以返回告知框架层是否需要重试
98 |
99 | 中断后续的工作器执行有两个方法:
100 |
101 | 方法1: 通过返回状态为失败的 WorkerResult 返回值。一旦工作管理器的状态为 IsFail 状态,将会阻止所有的没有标记 CanRunWhenFail 为 true 的工作器的执行。换句话说就是除了哪些不断成功或失败状态都要执行的 Worker 工作器之外,其他的工作器都将不会执行,包括 SetContext 里面的委托转换也不会执行
102 |
103 | 方法2: 通过抛出异常的方式,通过 dotnet 里面的异常可以让后续逻辑炸掉不再执行
104 |
105 | 以上两个方法都是框架推荐使用的。框架设计的偏好是如果在重视性能的情况下,尽量使用方法1的方式中断执行。如果是在复杂的业务逻辑,有大量业务逻辑穿插在工作过程之外,则可以方便通过方法2进行中断
106 |
107 | ### 在 Worker 里执行其他 Worker 工作器
108 |
109 | 在一个 Worker 里面,可以执行其他的 Worker 工作器,如此可以比较自由的实现分支逻辑,套娃决定执行工作器
110 |
111 | 例子如下:
112 |
113 | 假定有一个 Worker2 工作器,定义如下:
114 |
115 | ```csharp
116 | class Worker2 : MessageWorker
117 | {
118 | protected override ValueTask> DoInnerAsync(InputType input)
119 | {
120 | return SuccessTask(new OutputType());
121 | }
122 | }
123 |
124 | record InputType();
125 |
126 | record OutputType();
127 | ```
128 |
129 | 有另一个 Worker1 工作器,可以在 Worker1 里面执行 Worker2 工作器:
130 |
131 | ```csharp
132 | class Worker1 : MessageWorkerBase
133 | {
134 | public override async ValueTask Do(IWorkerContext context)
135 | {
136 | await Manager
137 | .GetWorker()
138 | .RunAsync(new InputType());
139 |
140 | return WorkerResult.Success();
141 | }
142 | }
143 | ```
144 |
145 | ### 委托工作器
146 |
147 | 有一些非常小且轻的逻辑,也想加入到工作过程里面,但不想为此定义一个单独的工作器。可以试试委托工作器,如以下代码例子
148 |
149 | ```csharp
150 | var delegateMessageWorker = new DelegateMessageWorker(_ =>
151 | {
152 | // 在这里编写委托工作器的业务内容
153 | });
154 |
155 | var result = await messageWorkerManager.RunWorker(delegateMessageWorker);
156 | ```
157 |
158 | 如果连 DelegateMessageWorker 都不想创建,那也可以直接使用 MessageWorkerManager 的 RunWorker 方法传入委托,如以下代码例子
159 |
160 | ```csharp
161 | await messageWorkerManager.RunWorker((IWorkerContext context) =>
162 | {
163 | // 在这里编写委托工作器的业务内容
164 | });
165 | ```
166 |
167 | ## 相似的项目
168 |
169 | https://github.com/danielgerlag/workflow-core
--------------------------------------------------------------------------------
/build/Version.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1.0.0
4 |
5 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager.Tests/LightWorkFlowManager.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | false
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager.Tests/MessageWorkerManagerTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using DC.LightWorkFlowManager;
4 | using DC.LightWorkFlowManager.Contexts;
5 | using DC.LightWorkFlowManager.Exceptions;
6 | using DC.LightWorkFlowManager.Protocols;
7 | using DC.LightWorkFlowManager.Workers;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using Microsoft.VisualStudio.TestTools.UnitTesting;
10 |
11 | using MSTest.Extensions.Contracts;
12 |
13 | namespace LightWorkFlowManager.Tests;
14 |
15 | [TestClass]
16 | public class MessageWorkerManagerTest
17 | {
18 | private const int UnknownError = 7000;
19 |
20 | [ContractTestCase]
21 | public void RunFail()
22 | {
23 | "执行工作器,抛出工作器异常,异常设置可以重试,重试返回成功,管理器状态是成功".Test(async () =>
24 | {
25 | var count = 0;
26 | var messageWorkerManager = GetTestMessageWorkerManager();
27 |
28 | var delegateMessageWorker = new DelegateMessageWorker(_ =>
29 | {
30 | count++;
31 |
32 | if (count == 1)
33 | // 异常设置可以重试
34 | throw new MessageWorkerException(UnknownError, true);
35 | return ValueTask.CompletedTask;
36 | });
37 |
38 | var result = await messageWorkerManager.RunWorker(delegateMessageWorker);
39 | Assert.AreEqual(true, result.IsSuccess);
40 | Assert.AreEqual(false, messageWorkerManager.MessageWorkerStatus.IsFail);
41 | });
42 |
43 | "执行工作器,不断抛出异常,可以将异常抛出管理器,管理器状态是失败".Test(async () =>
44 | {
45 | var messageWorkerManager = GetTestMessageWorkerManager();
46 |
47 | var delegateMessageWorker = new DelegateMessageWorker(_ =>
48 | {
49 | throw new ArgumentException();
50 | #pragma warning disable CS0162
51 | return ValueTask.CompletedTask;
52 | #pragma warning restore CS0162
53 | });
54 |
55 | await Assert.ThrowsExceptionAsync(async () =>
56 | {
57 | await messageWorkerManager.RunWorker(delegateMessageWorker);
58 | });
59 |
60 | Assert.AreEqual(true, messageWorkerManager.MessageWorkerStatus.IsFail);
61 | });
62 |
63 | "执行工作器,先抛出任意异常,重试时返回成功,管理器状态是成功".Test(async () =>
64 | {
65 | var messageWorkerManager = GetTestMessageWorkerManager();
66 | var count = 0;
67 |
68 | var delegateMessageWorker = new DelegateMessageWorker(_ =>
69 | {
70 | count++;
71 |
72 | if (count == 3)
73 | return ValueTask.FromResult(WorkerResult.Success());
74 | throw new ArgumentException();
75 | });
76 |
77 | var result = await messageWorkerManager.RunWorker(delegateMessageWorker);
78 | Assert.AreEqual(true, result.IsSuccess);
79 | Assert.AreEqual(3, count);
80 | Assert.AreEqual(false, messageWorkerManager.MessageWorkerStatus.IsFail);
81 | });
82 |
83 | "执行一个输入参数不存在工作器,抛出阻断异常".Test(async () =>
84 | {
85 | var messageWorkerManager = GetTestMessageWorkerManager();
86 |
87 | await Assert.ThrowsExceptionAsync(async () =>
88 | {
89 | // 没有给工作器设置参数
90 | await messageWorkerManager.RunWorker();
91 | });
92 |
93 | Assert.AreEqual(true, messageWorkerManager.MessageWorkerStatus.IsFail);
94 | });
95 |
96 | "执行一个一直返回 WorkerResult 失败的工作器,管理器的状态是失败".Test(async () =>
97 | {
98 | var messageWorkerManager = GetTestMessageWorkerManager();
99 |
100 | var delegateMessageWorker = new DelegateMessageWorker(_ =>
101 | {
102 | return ValueTask.FromResult(WorkerResult.Fail(UnknownError));
103 | });
104 |
105 | await messageWorkerManager.RunWorker(delegateMessageWorker);
106 |
107 | // 管理器的状态是失败
108 | Assert.AreEqual(true, messageWorkerManager.MessageWorkerStatus.IsFail);
109 | });
110 |
111 | "一个工作器先执行一次失败,再执行一次成功,失败通过 WorkerResult 返回,最后管理器的状态应该是成功".Test(async () =>
112 | {
113 | var messageWorkerManager = GetTestMessageWorkerManager();
114 |
115 | var failTestMessageWorker = new FailTestMessageWorker();
116 | await messageWorkerManager.RunWorker(failTestMessageWorker);
117 |
118 | // 最后管理器的状态应该是成功
119 | Assert.AreEqual(false, messageWorkerManager.MessageWorkerStatus.IsFail);
120 | });
121 | }
122 |
123 | public static MessageWorkerManager GetTestMessageWorkerManager(IServiceProvider? serviceProvider = null,
124 | string? taskId = null, string? taskName = null, int retryCount = 3)
125 | {
126 | var messageWorkerManager = new MessageWorkerManager(taskId ?? Guid.NewGuid().ToString(), taskName ?? "Test",
127 | serviceProvider?.CreateScope() ?? BuildServiceProvider().CreateScope(), retryCount);
128 | return messageWorkerManager;
129 | }
130 |
131 | public static IServiceProvider BuildServiceProvider()
132 | {
133 | var serviceCollection = new ServiceCollection();
134 | serviceCollection.AddLogging();
135 | serviceCollection.AddTransient();
136 |
137 | var serviceProvider = serviceCollection.BuildServiceProvider();
138 | return serviceProvider;
139 | }
140 |
141 | private class RequestInputMessageWorker : MessageWorker
142 | {
143 | protected override ValueTask DoAsync(TestInputFoo input)
144 | {
145 | return ValueTask.FromResult(WorkerResult.Success());
146 | }
147 | }
148 |
149 | private class TestInputFoo
150 | {
151 | }
152 |
153 | private class FailTestMessageWorker : MessageWorkerBase
154 | {
155 | public bool Success { get; set; }
156 |
157 | public override ValueTask Do(IWorkerContext context)
158 | {
159 | var result = Success ? WorkerResult.Success() : WorkerResult.Fail(UnknownError);
160 | Success = !Success;
161 |
162 | return ValueTask.FromResult(result);
163 | }
164 | }
165 | }
--------------------------------------------------------------------------------
/src/LightWorkFlowManager.Tests/MessageWorkerTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using DC.LightWorkFlowManager;
4 | using DC.LightWorkFlowManager.Contexts;
5 | using DC.LightWorkFlowManager.Protocols;
6 | using DC.LightWorkFlowManager.Workers;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using Microsoft.VisualStudio.TestTools.UnitTesting;
9 |
10 | using MSTest.Extensions.Contracts;
11 |
12 | namespace LightWorkFlowManager.Tests;
13 |
14 | [TestClass]
15 | public class MessageWorkerTest
16 | {
17 | [ContractTestCase]
18 | public void RunWorkerOnWorker()
19 | {
20 | "可以在一个 Worker 里面套另一个 Worker 的运行".Test(async () =>
21 | {
22 | var serviceCollection = new ServiceCollection();
23 | serviceCollection
24 | .AddTransient()
25 | .AddTransient()
26 | .AddLogging();
27 |
28 | await using var serviceProvider = serviceCollection.BuildServiceProvider();
29 | var serviceScope = serviceProvider.CreateScope();
30 |
31 | await using var messageWorkerManager = new MessageWorkerManager(taskId: Guid.NewGuid().ToString(), taskName: "TestRunWorkerOnWorker", serviceScope);
32 |
33 | var result = await messageWorkerManager.RunWorker();
34 | Assert.AreEqual(true, result.IsSuccess);
35 | });
36 | }
37 |
38 | class Worker1 : MessageWorkerBase
39 | {
40 | public override async ValueTask Do(IWorkerContext context)
41 | {
42 | await Manager
43 | .GetWorker()
44 | .RunAsync(new InputType());
45 |
46 | return WorkerResult.Success();
47 | }
48 | }
49 |
50 | class Worker2 : MessageWorker
51 | {
52 | protected override ValueTask> DoInnerAsync(InputType input)
53 | {
54 | return SuccessTask(new OutputType());
55 | }
56 | }
57 |
58 | record InputType();
59 |
60 | record OutputType();
61 | }
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Contexts/IWorkerContext.cs:
--------------------------------------------------------------------------------
1 | using DC.LightWorkFlowManager.Exceptions;
2 |
3 | namespace DC.LightWorkFlowManager.Contexts;
4 |
5 | ///
6 | /// 工作器上下文信息
7 | ///
8 | /// 上下文信息是带有数据的,基本上只和当前的工作过程有关的数据。和依赖注入的服务是两个不同的方向,这里的上下文信息更多的是保存一些和当前正在工作的过程有关的数据,多个不同的任务的数据不互通,相互是分开的
9 | public interface IWorkerContext
10 | {
11 | ///
12 | /// 获取上下文信息
13 | ///
14 | ///
15 | /// 如果获取不到,返回空
16 | T? GetContext();
17 |
18 | ///
19 | /// 设置上下文信息
20 | ///
21 | ///
22 | ///
23 | void SetContext(T context);
24 | }
25 |
26 | ///
27 | /// 工作器上下文信息扩展类型
28 | ///
29 | public static class MessageContextExtension
30 | {
31 | ///
32 | /// 获取一定存在的上下文信息
33 | ///
34 | ///
35 | ///
36 | ///
37 | /// 如果上下文信息不存在,就抛出异常
38 | public static T GetEnsureContext(this IWorkerContext workerContext)
39 | {
40 | var context = workerContext.GetContext();
41 |
42 | if (context == null)
43 | {
44 | throw new WorkerContextNotFoundException(typeof(T).FullName!);
45 | }
46 |
47 | return context;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Contexts/WorkFlowErrorCode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 |
4 | namespace DC.LightWorkFlowManager.Contexts;
5 |
6 | ///
7 | /// 错误码
8 | ///
9 | /// 之前只是返回一个数值,不方便其他接入方了解问题原因。也不方便自己看 ES 日志,为了解决此问题,将可读的信息也记录
10 | public readonly struct WorkFlowErrorCode : IEquatable
11 | {
12 | ///
13 | /// 创建错误码
14 | ///
15 | public WorkFlowErrorCode(int code, string message)
16 | {
17 | Code = code;
18 | Message = message;
19 |
20 | ErrorCodeDictionary[code] = this;
21 | }
22 |
23 | ///
24 | /// 错误码
25 | ///
26 | public int Code { get; }
27 |
28 | ///
29 | /// 给人类的信息
30 | ///
31 | public string Message { get; }
32 |
33 | ///
34 | /// 表示成功
35 | ///
36 | public static WorkFlowErrorCode Ok => new WorkFlowErrorCode(0, "Ok");
37 |
38 | ///
39 | /// 追加信息
40 | ///
41 | ///
42 | ///
43 | public WorkFlowErrorCode AppendMessage(string? appendMessage)
44 | {
45 | if (appendMessage == null)
46 | {
47 | return this;
48 | }
49 | else
50 | {
51 | return new WorkFlowErrorCode(Code, Message + " " + appendMessage);
52 | }
53 | }
54 |
55 | ///
56 | /// 隐式转换为 int 类型
57 | ///
58 | ///
59 | public static implicit operator int(WorkFlowErrorCode code)
60 | {
61 | return code.Code;
62 | }
63 |
64 | ///
65 | /// 从 int 类型隐式转换为错误信息
66 | ///
67 | ///
68 | public static implicit operator WorkFlowErrorCode(int code)
69 | {
70 | if (ErrorCodeDictionary.TryGetValue(code, out var value))
71 | {
72 | return value;
73 | }
74 |
75 | if (code == WorkFlowErrorCode.Ok.Code)
76 | {
77 | return new WorkFlowErrorCode(code, "Ok");
78 | }
79 |
80 | return new WorkFlowErrorCode(code, string.Empty);
81 | }
82 |
83 | ///
84 | public override string ToString() => $"{Code} {Message}";
85 |
86 | private static readonly ConcurrentDictionary ErrorCodeDictionary =
87 | new ConcurrentDictionary();
88 |
89 | ///
90 | public bool Equals(WorkFlowErrorCode other)
91 | {
92 | return Code == other.Code;
93 | }
94 |
95 | ///
96 | public override bool Equals(object? obj)
97 | {
98 | return obj is WorkFlowErrorCode other && Equals(other);
99 | }
100 |
101 | ///
102 | public override int GetHashCode()
103 | {
104 | return Code;
105 | }
106 |
107 | ///
108 | /// 判断相等
109 | ///
110 | ///
111 | ///
112 | ///
113 | public static bool operator ==(WorkFlowErrorCode left, WorkFlowErrorCode right)
114 | {
115 | return left.Equals(right);
116 | }
117 |
118 | ///
119 | /// 判断不相等
120 | ///
121 | ///
122 | ///
123 | ///
124 | public static bool operator !=(WorkFlowErrorCode left, WorkFlowErrorCode right)
125 | {
126 | return !left.Equals(right);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Contexts/WorkerContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 |
4 | namespace DC.LightWorkFlowManager.Contexts;
5 |
6 | public class WorkerContext : IWorkerContext
7 | {
8 | [System.Diagnostics.DebuggerStepThrough]
9 | public T? GetContext()
10 | {
11 | if (_contextDictionary.TryGetValue(typeof(T), out var value))
12 | {
13 | return (T?) value;
14 | }
15 |
16 | return default;
17 | }
18 |
19 | public void SetContext(T context)
20 | {
21 | _contextDictionary[typeof(T)] = context;
22 | }
23 |
24 | private readonly ConcurrentDictionary _contextDictionary = new ConcurrentDictionary();
25 | }
26 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Exceptions/IWorkFlowException.cs:
--------------------------------------------------------------------------------
1 | namespace DC.LightWorkFlowManager.Exceptions;
2 |
3 | ///
4 | /// 表示当前是一个工作过程的异常
5 | ///
6 | public interface IWorkFlowException
7 | {
8 | }
9 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Exceptions/MessageWorkerException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using DC.LightWorkFlowManager.Contexts;
3 |
4 | namespace DC.LightWorkFlowManager.Exceptions;
5 |
6 | ///
7 | /// 工作过程的异常,使用用来扔出明确异常,打断后续执行
8 | ///
9 | public class MessageWorkerException : WorkFlowException
10 | {
11 | ///
12 | /// 工作过程的异常
13 | ///
14 | ///
15 | /// 默认 false 表示不能重试
16 | public MessageWorkerException(WorkFlowErrorCode errorCode, bool canRetryWorker = false)
17 | {
18 | ErrorCode = errorCode;
19 | CanRetryWorker = canRetryWorker;
20 | }
21 |
22 | ///
23 | /// 工作过程的异常
24 | ///
25 | public MessageWorkerException(WorkFlowErrorCode errorCode, Exception innerException) : base(errorCode.Message, innerException)
26 | {
27 | ErrorCode = errorCode;
28 | CanRetryWorker = false;
29 | }
30 |
31 | ///
32 | /// 是否可以重试
33 | ///
34 | public bool CanRetryWorker { get; }
35 | public WorkFlowErrorCode ErrorCode { get; }
36 |
37 | public override string Message => ErrorCode.Message;
38 | }
39 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Exceptions/MessageWorkerInputArgumentException.cs:
--------------------------------------------------------------------------------
1 | using DC.LightWorkFlowManager.Contexts;
2 |
3 | namespace DC.LightWorkFlowManager.Exceptions;
4 |
5 | ///
6 | /// 工作过程传入的参数异常,证明前置步骤或用户输入数据有误,抛出此异常则不再重试 Worker 任务
7 | ///
8 | public class MessageWorkerInputArgumentException : MessageWorkerException
9 | {
10 | public MessageWorkerInputArgumentException(WorkFlowErrorCode errorCode) : base(errorCode,canRetryWorker: false)
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Exceptions/MessageWorkerInputNotFoundException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DC.LightWorkFlowManager.Exceptions;
4 |
5 | ///
6 | /// 找不到输入的异常,基本上就是业务代码写错
7 | ///
8 | public class MessageWorkerInputNotFoundException : InvalidOperationException, IWorkFlowException
9 | {
10 | public MessageWorkerInputNotFoundException(string? message) : base(message)
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Exceptions/WorkFlowException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.Serialization;
3 |
4 | namespace DC.LightWorkFlowManager.Exceptions;
5 |
6 | ///
7 | /// 表示工作过程的异常
8 | ///
9 | public abstract class WorkFlowException : Exception, IWorkFlowException
10 | {
11 | protected WorkFlowException()
12 | {
13 | }
14 |
15 | protected WorkFlowException(SerializationInfo info, StreamingContext context) : base(info, context)
16 | {
17 | }
18 |
19 | protected WorkFlowException(string? message) : base(message)
20 | {
21 | }
22 |
23 | protected WorkFlowException(string? message, Exception? innerException) : base(message, innerException)
24 | {
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Exceptions/WorkerContextNotFoundException.cs:
--------------------------------------------------------------------------------
1 | namespace DC.LightWorkFlowManager.Exceptions;
2 |
3 | public class WorkerContextNotFoundException : WorkFlowException
4 | {
5 | public WorkerContextNotFoundException(string key)
6 | {
7 | Key = key;
8 | }
9 |
10 | public string Key { get; }
11 |
12 | public override string Message => $"Can not find {Key}";
13 | }
14 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/LightWorkFlowManager.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | true
6 | enable
7 |
8 | true
9 |
10 | dotnetCampus.LightWorkFlowManager
11 | DC.LightWorkFlowManager
12 |
13 |
14 |
15 |
16 |
17 |
18 | true
19 |
20 |
21 | true
22 |
23 |
24 |
25 |
26 |
27 | true
28 |
29 |
30 |
31 | true
32 | snupkg
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/MessageWorkerManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Runtime.ExceptionServices;
6 | using System.Threading.Tasks;
7 | using DC.LightWorkFlowManager.Contexts;
8 | using DC.LightWorkFlowManager.Exceptions;
9 | using DC.LightWorkFlowManager.Monitors;
10 | using DC.LightWorkFlowManager.Protocols;
11 | using DC.LightWorkFlowManager.Workers;
12 | using Microsoft.Extensions.DependencyInjection;
13 | using Microsoft.Extensions.Logging;
14 |
15 | namespace DC.LightWorkFlowManager;
16 |
17 | ///
18 | /// 工作器管理
19 | ///
20 | public class MessageWorkerManager : IAsyncDisposable
21 | {
22 | ///
23 | /// 创建工作器管理
24 | ///
25 | ///
26 | /// 任务名,任务类型,如 PDF 解析或 PPT 解析等
27 | ///
28 | /// 每个工作器的失败重试次数,默认三次
29 | /// 参数上下文信息
30 | ///
31 | public MessageWorkerManager(string taskId, string taskName, IServiceScope serviceScope, int retryCount = 3,
32 | IWorkerContext? context = null, IWorkerRunMonitor? workerRunMonitor = null)
33 | {
34 | _serviceScope = serviceScope;
35 | context ??= new WorkerContext();
36 |
37 | ServiceProvider = serviceScope.ServiceProvider;
38 | TaskId = taskId;
39 | TaskName = taskName;
40 | Context = context;
41 | RetryCount = retryCount;
42 |
43 | context.SetContext(this);
44 | context.SetContext(MessageWorkerStatus);
45 | context.SetContext(serviceScope);
46 |
47 | Logger = ServiceProvider.GetRequiredService>();
48 | WorkerRunMonitor = workerRunMonitor;
49 | }
50 |
51 | private readonly IServiceScope _serviceScope;
52 |
53 | ///
54 | /// 任务 Id 用于追踪
55 | ///
56 | public string TaskId { get; }
57 |
58 | ///
59 | /// 任务名,任务类型,如 PDF 解析或 PPT 解析等
60 | ///
61 | public string TaskName { get; }
62 |
63 | ///
64 | /// 重试次数
65 | ///
66 | public int RetryCount { get; }
67 |
68 | ///
69 | /// 服务提供器
70 | ///
71 | public IServiceProvider ServiceProvider { get; }
72 |
73 | ///
74 | /// 参数上下文
75 | ///
76 | public IWorkerContext Context { get; }
77 |
78 | ///
79 | /// 当前的状态
80 | ///
81 | public MessageWorkerStatus MessageWorkerStatus { get; set; } = new MessageWorkerStatus();
82 |
83 | ///
84 | /// 用来监控执行状态
85 | ///
86 | protected IWorkerRunMonitor? WorkerRunMonitor { get; set; }
87 |
88 | ///
89 | /// 工作器执行栈,用于调试,以及用于清理
90 | ///
91 | /// 清理采用后执行的先清理的方式
92 | private readonly Stack _workerStack = new Stack();
93 |
94 | ///
95 | /// 日志
96 | ///
97 | public ILogger Logger { get; protected set; }
98 |
99 | ///
100 | /// 设置上下文信息。设计上要求一个类型对应一个参数,不允许相同的类型作为不同的参数
101 | ///
102 | ///
103 | ///
104 | ///
105 | public MessageWorkerManager SetContext(T context)
106 | {
107 | Context.SetContext(context);
108 | return this;
109 | }
110 |
111 | ///
112 | /// 根据现有的参数设置上下文信息
113 | ///
114 | /// 现有的参数类型
115 | /// 转换后的参数类型
116 | ///
117 | /// 如果前置步骤失败,即 为 IsFail 时,将不执行委托内容
118 | ///
119 | public MessageWorkerManager SetContext(Func worker)
120 | {
121 | if (MessageWorkerStatus.IsFail)
122 | {
123 | // 失败就不给转换
124 | return this;
125 | }
126 |
127 | var input = Context.GetEnsureContext();
128 | var output = worker(input);
129 | Context.SetContext(output);
130 | return this;
131 | }
132 |
133 | ///
134 | /// 获取工作器,获取到的工作器将会被注入信息
135 | ///
136 | ///
137 | ///
138 | public T GetWorker() where T : IMessageWorker
139 | {
140 | var messageWorker = ServiceProvider.GetRequiredService();
141 | SetManager(messageWorker as IMessageWorkerManagerSensitive);
142 | return messageWorker;
143 | }
144 |
145 | ///
146 | /// 执行委托工作器,执行的内容为 参数的内容
147 | ///
148 | ///
149 | ///
150 | ///
151 | /// 此委托代表的工作器名,用于调试和埋点上报
152 | /// 是否在当前 前置步骤已失败时,依然可以执行。默认为 false 表示在前置步骤失败时,不执行
153 | ///
154 | public ValueTask RunWorker(Func messageTask, string? workerName = null, bool canRunWhenFail = false)
155 | {
156 | var worker = new DelegateMessageWorker(messageTask, workerName ?? messageTask.Method.DeclaringType?.FullName, canRunWhenFail);
157 | return RunWorker(worker);
158 | }
159 |
160 | ///
161 | /// 执行委托工作器,执行的内容为 参数的内容
162 | ///
163 | ///
164 | ///
165 | ///
166 | /// 此委托代表的工作器名,用于调试和埋点上报
167 | /// 是否在当前 前置步骤已失败时,依然可以执行。默认为 false 表示在前置步骤失败时,不执行
168 | ///
169 | public ValueTask RunWorker(Func> messageTask, string? workerName = null, bool canRunWhenFail = false)
170 | {
171 | var worker = new DelegateMessageWorker(messageTask, workerName ?? messageTask.Method.DeclaringType?.FullName, canRunWhenFail);
172 | return RunWorker(worker);
173 | }
174 |
175 | ///
176 | /// 执行委托工作器,执行的内容为 参数的内容
177 | ///
178 | ///
179 | ///
180 | /// 此委托代表的工作器名,用于调试和埋点上报
181 | /// 是否在当前 前置步骤已失败时,依然可以执行。默认为 false 表示在前置步骤失败时,不执行
182 | ///
183 | public ValueTask RunWorker(Func messageTask, string? workerName = null, bool canRunWhenFail = false)
184 | {
185 | var worker = new DelegateMessageWorker(messageTask, workerName ?? messageTask.Method.DeclaringType?.FullName, canRunWhenFail);
186 | return RunWorker(worker);
187 | }
188 |
189 | ///
190 | /// 执行委托工作器,执行的内容为 参数的内容
191 | ///
192 | ///
193 | /// 此委托代表的工作器名,用于调试和埋点上报
194 | /// 是否在当前 前置步骤已失败时,依然可以执行。默认为 false 表示在前置步骤失败时,不执行
195 | ///
196 | public ValueTask RunWorker(Func messageTask, string? workerName = null, bool canRunWhenFail = false)
197 | {
198 | var worker = new DelegateMessageWorker(_ => messageTask(), workerName ?? messageTask.Method.DeclaringType?.FullName, canRunWhenFail);
199 | return RunWorker(worker);
200 | }
201 |
202 | ///
203 | /// 执行委托工作器,执行的内容为 参数的内容
204 | ///
205 | ///
206 | ///
207 | /// 此委托代表的工作器名,用于调试和埋点上报
208 | /// 是否在当前 前置步骤已失败时,依然可以执行。默认为 false 表示在前置步骤失败时,不执行
209 | ///
210 | public ValueTask RunWorker(Action messageTask, string? workerName = null, bool canRunWhenFail = false)
211 | {
212 | var worker = new DelegateMessageWorker(input =>
213 | {
214 | messageTask(input);
215 | return ValueTask.CompletedTask;
216 | }, workerName ?? messageTask.Method.DeclaringType?.FullName, canRunWhenFail);
217 | return RunWorker(worker);
218 | }
219 |
220 | ///
221 | /// 执行委托工作器,执行的内容为 参数的内容
222 | ///
223 | ///
224 | /// 此委托代表的工作器名,用于调试和埋点上报
225 | /// 是否在当前 前置步骤已失败时,依然可以执行。默认为 false 表示在前置步骤失败时,不执行
226 | ///
227 | public ValueTask RunWorker(Action messageTask, string? workerName = null, bool canRunWhenFail = false)
228 | {
229 | var worker = new DelegateMessageWorker(messageTask, workerName ?? messageTask.Method.DeclaringType?.FullName, canRunWhenFail);
230 | return RunWorker(worker);
231 | }
232 |
233 | ///
234 | /// 执行委托工作器,执行的内容为 参数的内容
235 | ///
236 | ///
237 | /// 此委托代表的工作器名,用于调试和埋点上报
238 | /// 是否在当前 前置步骤已失败时,依然可以执行。默认为 false 表示在前置步骤失败时,不执行
239 | ///
240 | public ValueTask RunWorker(Action messageTask, string? workerName = null, bool canRunWhenFail = false)
241 | {
242 | var worker = new DelegateMessageWorker(_ => messageTask(), workerName ?? messageTask.Method.DeclaringType?.FullName, canRunWhenFail);
243 | return RunWorker(worker);
244 | }
245 |
246 | ///
247 | /// 执行委托工作器,执行的内容为 参数的内容
248 | ///
249 | ///
250 | /// 此委托代表的工作器名,用于调试和埋点上报
251 | /// 是否在当前 前置步骤已失败时,依然可以执行。默认为 false 表示在前置步骤失败时,不执行
252 | ///
253 | public ValueTask RunWorker(Func messageTask, string? workerName = null, bool canRunWhenFail = false)
254 | {
255 | var worker = new DelegateMessageWorker(messageTask, workerName ?? messageTask.Method.DeclaringType?.FullName, canRunWhenFail);
256 | return RunWorker(worker);
257 | }
258 |
259 | ///
260 | /// 执行工作器
261 | ///
262 | ///
263 | ///
264 | public ValueTask RunWorker() where TWorker : IMessageWorker
265 | {
266 | var worker = ServiceProvider.GetRequiredService();
267 | return RunWorker(worker);
268 | }
269 |
270 | ///
271 | /// 执行工作器
272 | ///
273 | ///
274 | ///
275 | public virtual async ValueTask RunWorker(IMessageWorker worker)
276 | {
277 | SetManager(worker as IMessageWorkerManagerSensitive);
278 | _workerStack.Push(worker);
279 | worker.TaskId = TaskId;
280 |
281 | if (MessageWorkerStatus.IsFail)
282 | {
283 | // 如果当前的状态是失败,且当前的 Worker 不能在失败时运行,那就返回吧
284 | if (!worker.CanRunWhenFail)
285 | {
286 | IgnoreWorkerRunOnFailStatus(worker);
287 |
288 | return GetFailResult();
289 | }
290 | }
291 |
292 | try
293 | {
294 | var result = await RunWithRetry();
295 | OnWorkerRunFinish(worker, result);
296 |
297 | return result;
298 | }
299 | catch (Exception e)
300 | {
301 | OnWorkerRunException(worker, e);
302 | MessageWorkerStatus.LastException = e;
303 |
304 | // 继续对外抛出
305 | throw;
306 | }
307 |
308 | // 运行工作任务的核心入口
309 | async ValueTask RunWorkerCoreInner()
310 | {
311 | WorkerRunMonitor?.OnWorkerStart(worker);
312 | try
313 | {
314 | // 可以在这里打断点,调试被执行的逻辑
315 | var result = await RunWorkerCore(worker);
316 | WorkerRunMonitor?.OnWorkerFinish(worker, result);
317 | return result;
318 | }
319 | catch (Exception e)
320 | {
321 | WorkerRunMonitor?.OnWorkerException(worker, e);
322 | throw;
323 | }
324 | }
325 |
326 | async ValueTask RunWithRetry()
327 | {
328 | Exception? exception = null;
329 |
330 | for (int i = 0; i < RetryCount; i++)
331 | {
332 | var isFirstRun = i == 0;
333 | // 是否最后一次执行
334 | bool isLastRun = i == RetryCount - 1;
335 |
336 | if (!isFirstRun)
337 | {
338 | // 非首次执行,等待一下吧
339 | await Task.Delay(worker.RetryDelayTime);
340 | }
341 |
342 | try
343 | {
344 | var workerResult = await RunWorkerCoreInner();
345 | if
346 | (
347 | // 不是最后一次执行的状态,才可以允许回到循环重新执行
348 | !isLastRun
349 | // 如果是失败,那才会有重试的需求。成功就立刻返回
350 | && workerResult.IsFail
351 | // 如果失败记录里面允许重试的话,才可以进行重试。例如有些是明确的参数错误,重试也没有用的,具体工作器返回时将会记录不能重试,此时就听工作器的,不重试
352 | && workerResult.CanRetry
353 | // 如果要重试,还需要判断一下工作器本身是否支持重试。理论上如果返回结果是可以重试,基本工作器都可以重试
354 | && worker.CanRetry
355 | )
356 | {
357 | // 如果失败了,且可以重试的,那就执行继续逻辑吧
358 | continue;
359 | }
360 | else
361 | {
362 | return workerResult;
363 | }
364 | }
365 | catch (MessageWorkerInputNotFoundException)
366 | {
367 | // 输入参数不存在了,那就是阻断异常,基本都是业务代码写错的
368 | throw;
369 | }
370 | catch (Exception e)
371 | {
372 | exception = e;
373 |
374 | // 如果是最后一次执行,那就立刻抛出,不进行等待
375 | // 如果 Worker 自身,或者是框架不允许重试,那就不重试
376 | if (isLastRun || !worker.CanRetry || !CanRetry(exception, i))
377 | {
378 | throw;
379 | }
380 | }
381 | }
382 |
383 | Debug.Assert(exception is not null);
384 | ExceptionDispatchInfo.Throw(exception);
385 | // 理论上是不会进入这里
386 | throw exception;
387 | }
388 | }
389 |
390 | #region 框架重写
391 |
392 | ///
393 | /// 实际执行工作器的方法
394 | ///
395 | /// 方便业务方重写,从而方便打断点调试
396 | ///
397 | ///
398 | protected virtual ValueTask RunWorkerCore(IMessageWorker worker)
399 | // 可以在这里打断点,调试被执行的逻辑
400 | => worker.Do(Context);
401 |
402 | ///
403 | /// 在当前是失败的状态下,跳过工作器的执行
404 | ///
405 | ///
406 | protected virtual void IgnoreWorkerRunOnFailStatus(IMessageWorker worker)
407 | {
408 | //Logger.CCloudInfo($"Fail run {worker.WorkerName}. {MessageWorkerStatus}");
409 | }
410 |
411 | ///
412 | /// 当工作器开始执行触发
413 | ///
414 | ///
415 | protected virtual void OnWorkerRunStart(IMessageWorker worker)
416 | {
417 | }
418 |
419 | ///
420 | /// 当工作器执行触发,经过重试的结果
421 | ///
422 | ///
423 | ///
424 | protected virtual void OnWorkerRunFinish(IMessageWorker worker, WorkerResult result)
425 | {
426 | if (result.IsFail && !MessageWorkerStatus.IsFail)
427 | {
428 | // 失败了,记录一下
429 | RecordWorkerError(worker, result.ErrorCode);
430 | }
431 | }
432 |
433 | ///
434 | /// 当工作器执行异常时触发。只是用来做记录,不能用来吃掉异常
435 | ///
436 | ///
437 | ///
438 | protected virtual void OnWorkerRunException(IMessageWorker worker, Exception e)
439 | {
440 | if (e is MessageWorkerException messageWorkerException)
441 | {
442 | RecordWorkerError(worker, messageWorkerException.ErrorCode);
443 | }
444 | //else if (e is MessageWorkerInputNotFoundException)
445 | //{
446 | // RecordError(ResponseErrorCode.UnknownBusinessInternalError.AppendMessage(e.ToString()));
447 | //}
448 | else
449 | {
450 | RecordWorkerError(worker, new WorkFlowErrorCode(-1, e.ToString()));
451 | }
452 | }
453 |
454 | protected virtual void RecordWorkerError(IMessageWorker worker, WorkFlowErrorCode errorCode)
455 | {
456 | if (!MessageWorkerStatus.IsFail)
457 | {
458 | var appendMessage = $"FailWorker:{worker.WorkerName}";
459 | errorCode = errorCode.AppendMessage(appendMessage);
460 |
461 | MessageWorkerStatus.TrySetErrorCode(errorCode, worker);
462 | }
463 | }
464 |
465 | #endregion
466 |
467 |
468 | private void SetManager(IMessageWorkerManagerSensitive? messageWorkerManagerSensitive)
469 | => messageWorkerManagerSensitive?.SetMessageWorkerManager(this);
470 |
471 | ///
472 | /// 框架判断能否重试
473 | ///
474 | ///
475 | ///
476 | ///
477 | protected virtual bool CanRetry(Exception exception, int retryCount)
478 | {
479 | if (exception is MessageWorkerException messageWorker)
480 | {
481 | return messageWorker.CanRetryWorker;
482 | }
483 |
484 | return true;
485 | }
486 |
487 | private WorkerResult GetFailResult()
488 | {
489 | if (!MessageWorkerStatus.IsFail)
490 | {
491 | throw new InvalidOperationException($"只有已经失败时,才能获取失败结果");
492 | }
493 |
494 | return new WorkerResult(isSuccess: false, MessageWorkerStatus.Status,
495 | // 已经失败了的,就不需要重试
496 | canRetry: false);
497 | }
498 |
499 | internal WorkerResult GetFailResult()
500 | {
501 | if (!MessageWorkerStatus.IsFail)
502 | {
503 | throw new InvalidOperationException($"只有已经失败时,才能获取失败结果");
504 | }
505 |
506 | return new WorkerResult(MessageWorkerStatus.Status,
507 | // 已经失败了的,就不需要重试
508 | canRetry: false);
509 | }
510 |
511 | public async ValueTask DisposeAsync()
512 | {
513 | while (_workerStack.TryPop(out var worker))
514 | {
515 | try
516 | {
517 | await worker.OnDisposeAsync(Context);
518 | }
519 | catch
520 | {
521 | // Ignore
522 | }
523 | }
524 |
525 | _serviceScope.Dispose();
526 | }
527 |
528 | ///
529 | public override string ToString()
530 | {
531 | var name = TaskName;
532 | string status;
533 | if (MessageWorkerStatus.IsFail)
534 | {
535 | status = $"[Fail] {MessageWorkerStatus.Status}";
536 | }
537 | else
538 | {
539 | status = "[OK]";
540 | }
541 |
542 | return $"[{name}] {status} WorkerList:{string.Join('-', _workerStack.Reverse().Select(worker => worker.WorkerName))}";
543 | }
544 | }
545 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Monitors/IWorkerRunMonitor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using DC.LightWorkFlowManager.Protocols;
3 | using DC.LightWorkFlowManager.Workers;
4 |
5 | namespace DC.LightWorkFlowManager.Monitors;
6 |
7 | public interface IWorkerRunMonitor
8 | {
9 | void OnWorkerStart(IMessageWorker worker);
10 | void OnWorkerFinish(IMessageWorker worker, WorkerResult result);
11 | void OnWorkerException(IMessageWorker worker, Exception exception);
12 | }
13 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Protocols/MessageWorkerStatus.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using DC.LightWorkFlowManager.Contexts;
3 | using DC.LightWorkFlowManager.Workers;
4 |
5 | namespace DC.LightWorkFlowManager.Protocols;
6 |
7 | public class MessageWorkerStatus
8 | {
9 | public bool IsFail => Status != WorkFlowErrorCode.Ok;
10 |
11 | public WorkFlowErrorCode Status { get; private set; } = WorkFlowErrorCode.Ok;
12 |
13 | public Exception? LastException { get; set; }
14 |
15 | ///
16 | /// 失败的工作器
17 | ///
18 | public IMessageWorker? FailWorker { get; private set; }
19 |
20 | public void SetErrorCode(WorkFlowErrorCode errorCode) => Status = errorCode;
21 |
22 | public bool TrySetErrorCode(WorkFlowErrorCode errorCode, IMessageWorker failWorker)
23 | {
24 | if (IsFail)
25 | {
26 | return false;
27 | }
28 |
29 | Status = errorCode;
30 | FailWorker = failWorker;
31 |
32 | return true;
33 | }
34 |
35 | public override string ToString()
36 | {
37 | if (IsFail)
38 | {
39 | return $"[{Status.Code}] {Status.Message} {LastException}";
40 | }
41 | else
42 | {
43 | return "Ok";
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Protocols/WorkerResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 | using DC.LightWorkFlowManager.Contexts;
4 |
5 | namespace DC.LightWorkFlowManager.Protocols;
6 |
7 | public class WorkerResult
8 | {
9 | public WorkerResult(bool isSuccess, WorkFlowErrorCode errorCode, bool canRetry)
10 | {
11 | IsSuccess = isSuccess;
12 | ErrorCode = errorCode;
13 | CanRetry = canRetry;
14 |
15 | if (!isSuccess && errorCode.Code == WorkFlowErrorCode.Ok)
16 | {
17 | throw new ArgumentException($"失败时,禁止设置错误码为成功", nameof(errorCode));
18 | }
19 | }
20 |
21 | public virtual bool IsSuccess { get; }
22 |
23 | public bool IsFail => !IsSuccess;
24 |
25 | public WorkFlowErrorCode ErrorCode { get; }
26 |
27 | ///
28 | /// 是否可以重试
29 | ///
30 | public bool CanRetry { get; }
31 |
32 | public static WorkerResult Success() => new WorkerResult(true, WorkFlowErrorCode.Ok, false);
33 | public static WorkerResult Fail(WorkFlowErrorCode errorCode, bool canRetry=true) => new WorkerResult(false, errorCode, canRetry);
34 |
35 | public override string ToString()
36 | {
37 | if (IsSuccess)
38 | {
39 | return $"[Ok]";
40 | }
41 | else
42 | {
43 | return $"[Fail] {ErrorCode}";
44 | }
45 | }
46 | }
47 |
48 | public class WorkerResult : WorkerResult
49 | {
50 | public WorkerResult(T result) : base(isSuccess: true, WorkFlowErrorCode.Ok, canRetry: false)
51 | {
52 | Result = result;
53 | }
54 |
55 | public WorkerResult(WorkFlowErrorCode errorCode, bool canRetry) : base(isSuccess: false, errorCode, canRetry)
56 | {
57 | Result = default;
58 | }
59 |
60 | [MemberNotNullWhen(true, nameof(Result))]
61 | public override bool IsSuccess => Result != null;
62 | public T? Result { get; }
63 |
64 | public static implicit operator WorkerResult(T workerResult)
65 | {
66 | return new WorkerResult(workerResult);
67 | }
68 |
69 | public static implicit operator T?(WorkerResult workerResult) => workerResult.Result;
70 |
71 | public override string ToString()
72 | {
73 | if (IsSuccess)
74 | {
75 | return $"[Ok] {Result}";
76 | }
77 | else
78 | {
79 | return $"[Fail] {ErrorCode}";
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Workers/DelegateMessageWorker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using DC.LightWorkFlowManager.Contexts;
4 | using DC.LightWorkFlowManager.Protocols;
5 |
6 | namespace DC.LightWorkFlowManager.Workers;
7 |
8 | public class DelegateMessageWorker : MessageWorker
9 | {
10 | public DelegateMessageWorker(Func messageTask, string? workerName = null, bool canRunWhenFail = false)
11 | {
12 | _messageTask = messageTask;
13 | _workerName = workerName;
14 |
15 | CanRunWhenFail = canRunWhenFail;
16 | }
17 |
18 | public override string WorkerName => _workerName ?? base.WorkerName;
19 |
20 | private readonly string? _workerName;
21 |
22 | protected override async ValueTask DoAsync(TInput input)
23 | {
24 | await _messageTask(input);
25 | return WorkerResult.Success();
26 | }
27 |
28 | private readonly Func _messageTask;
29 | }
30 |
31 | public class DelegateMessageWorker : MessageWorker
32 | {
33 | public DelegateMessageWorker(Func messageTask, string? workerName = null, bool canRunWhenFail = false)
34 | {
35 | _workerName = workerName;
36 | _messageTask = input =>
37 | {
38 | var output = messageTask(input);
39 | return ValueTask.FromResult(output);
40 | };
41 |
42 | CanRunWhenFail = canRunWhenFail;
43 | }
44 |
45 | public DelegateMessageWorker(Func> messageTask, string? workerName = null, bool canRunWhenFail = false)
46 | {
47 | _messageTask = messageTask;
48 | _workerName = workerName;
49 |
50 | CanRunWhenFail = canRunWhenFail;
51 | }
52 |
53 | protected override async ValueTask> DoInnerAsync(TInput input)
54 | {
55 | return await _messageTask(input);
56 | }
57 |
58 | private readonly Func> _messageTask;
59 |
60 | public override string WorkerName => _workerName ?? base.WorkerName;
61 |
62 | private readonly string? _workerName;
63 | }
64 |
65 | public class DelegateMessageWorker : MessageWorkerBase
66 | {
67 | public DelegateMessageWorker(Action messageAction, string? workerName = null, bool canRunWhenFail=false)
68 | {
69 | _messageTask = c =>
70 | {
71 | messageAction(c);
72 | return ValueTask.FromResult(WorkerResult.Success());
73 | };
74 | _workerName = workerName;
75 |
76 | CanRunWhenFail = canRunWhenFail;
77 | }
78 |
79 | public DelegateMessageWorker(Func messageTask, string? workerName = null, bool canRunWhenFail=false)
80 | {
81 | _messageTask = async c =>
82 | {
83 | await messageTask(c);
84 | return WorkerResult.Success();
85 | };
86 | _workerName = workerName;
87 |
88 | CanRunWhenFail = canRunWhenFail;
89 | }
90 |
91 | public DelegateMessageWorker(Func> messageTask, string? workerName = null, bool canRunWhenFail = false)
92 | {
93 | _messageTask = messageTask;
94 | _workerName = workerName;
95 |
96 | CanRunWhenFail = canRunWhenFail;
97 | }
98 |
99 | public override string WorkerName => _workerName ?? base.WorkerName;
100 |
101 | private readonly string? _workerName;
102 |
103 | private readonly Func> _messageTask;
104 |
105 | public override ValueTask Do(IWorkerContext context)
106 | {
107 | return _messageTask(context);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Workers/IMessageWorker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using DC.LightWorkFlowManager.Contexts;
4 | using DC.LightWorkFlowManager.Protocols;
5 |
6 | namespace DC.LightWorkFlowManager.Workers;
7 |
8 | ///
9 | /// 工作器
10 | ///
11 | public interface IMessageWorker
12 | {
13 | string TaskId { get; set; }
14 |
15 | ///
16 | /// 工作器名,调试和上报时使用
17 | ///
18 | string WorkerName { get; }
19 |
20 | ///
21 | /// 是否可以重试
22 | ///
23 | bool CanRetry { get; }
24 |
25 | ///
26 | /// 重试的等待时间
27 | ///
28 | TimeSpan RetryDelayTime { get; }
29 |
30 | /////
31 | ///// 是否需要重试
32 | /////
33 | /////
34 | //bool NeedRetry();
35 |
36 | ///
37 | /// 遇到失败时,是否能执行
38 | ///
39 | ///
40 | bool CanRunWhenFail { get; }
41 |
42 | ValueTask Do(IWorkerContext context);
43 |
44 | ValueTask OnDisposeAsync(IWorkerContext context);
45 | }
46 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Workers/IMessageWorkerManagerSensitive.cs:
--------------------------------------------------------------------------------
1 | namespace DC.LightWorkFlowManager.Workers;
2 |
3 | ///
4 | /// 表示对 需要注入
5 | ///
6 | internal interface IMessageWorkerManagerSensitive
7 | {
8 | ///
9 | /// 在加入到 被调用
10 | ///
11 | ///
12 | void SetMessageWorkerManager(MessageWorkerManager manager);
13 | }
14 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Workers/MessageWorker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using DC.LightWorkFlowManager.Contexts;
4 | using DC.LightWorkFlowManager.Exceptions;
5 | using DC.LightWorkFlowManager.Protocols;
6 |
7 | namespace DC.LightWorkFlowManager.Workers;
8 |
9 | public abstract class MessageWorker : MessageWorkerBase
10 | {
11 | public sealed override ValueTask Do(IWorkerContext context)
12 | {
13 | var input = context.GetContext();
14 |
15 | if (input == null)
16 | {
17 | throw new MessageWorkerInputNotFoundException($"Do not find {typeof(TInput)} in {WorkerName} worker. 无法在{WorkerName}找到{typeof(TInput)}输入,请确保前置步骤完成输出或初始化进行输入");
18 | }
19 |
20 | return DoAsync(input);
21 | }
22 |
23 | protected abstract ValueTask DoAsync(TInput input);
24 | }
25 |
26 | public abstract class MessageWorker : MessageWorker
27 | {
28 | protected sealed override async ValueTask DoAsync(TInput input)
29 | {
30 | WorkerResult output = await DoInnerAsync(input);
31 |
32 | if (output.IsSuccess)
33 | {
34 | CurrentContext.SetContext(output.Result);
35 | }
36 |
37 | return output;
38 | }
39 |
40 | public ValueTask> RunAsync(Func converter)
41 | {
42 | var argument = CurrentContext.GetEnsureContext();
43 | var input = converter(argument);
44 | return RunAsync(input);
45 | }
46 |
47 | public ValueTask> RunAsync(TInput input)
48 | {
49 | ThrowNotManager();
50 |
51 | SetContext(input);
52 | return RunAsync();
53 | }
54 |
55 | public new async ValueTask> RunAsync()
56 | {
57 | ThrowNotManager();
58 |
59 | var manager = Manager;
60 | await manager.RunWorker(this);
61 | if (Status.IsFail)
62 | {
63 | return manager.GetFailResult();
64 | }
65 | else
66 | {
67 | return GetEnsureContext();
68 | }
69 | }
70 |
71 | protected abstract ValueTask> DoInnerAsync(TInput input);
72 |
73 | ///
74 | /// 返回且标记失败
75 | ///
76 | ///
77 | /// 是否需要重试
78 | ///
79 | protected WorkerResult Fail(WorkFlowErrorCode errorCode, bool retry = true)
80 | {
81 | return new WorkerResult(errorCode, retry);
82 | }
83 |
84 | protected ValueTask> FailTask(WorkFlowErrorCode errorCode, bool retry = true)
85 | => ValueTask.FromResult(Fail(errorCode, retry));
86 |
87 | protected WorkerResult Success(TOutput output)
88 | {
89 | return new WorkerResult(output);
90 | }
91 |
92 | protected ValueTask> SuccessTask(TOutput output)
93 | {
94 | var result = Success(output);
95 | return ValueTask.FromResult(result);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/LightWorkFlowManager/Workers/MessageWorkerBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using DC.LightWorkFlowManager.Contexts;
4 | using DC.LightWorkFlowManager.Protocols;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace DC.LightWorkFlowManager.Workers;
9 |
10 | ///
11 | /// 工作器基类
12 | ///
13 | public abstract class MessageWorkerBase : IMessageWorker, IMessageWorkerManagerSensitive
14 | {
15 | public string TaskId { get; set; } = null!;
16 | public virtual string WorkerName => GetType().Name;
17 |
18 | ///
19 | /// 设置或获取是否可以重试
20 | ///
21 | public virtual bool CanRetry { protected set; get; } = true;
22 |
23 | public virtual TimeSpan RetryDelayTime => TimeSpan.FromSeconds(1);
24 |
25 | ///
26 | /// 设置当前的 是否在 处于失败状态时能否运行
27 | ///
28 | public bool CanRunWhenFail { get; protected set; }
29 | // 默认遇到错误不能运行
30 | = false;
31 |
32 | public abstract ValueTask Do(IWorkerContext context);
33 |
34 | protected MessageWorkerStatus Status => GetEnsureContext();
35 | protected IWorkerContext CurrentContext => Manager.Context;
36 | protected IServiceProvider ServiceProvider => Manager.ServiceProvider;
37 | public ILogger Logger { get; private set; }
38 | // 框架注入
39 | = null!;
40 | protected MessageWorkerManager Manager { get; private set; }
41 | // 框架注入
42 | = null!;
43 |
44 | ///
45 | /// 从 获取服务,如果获取不到,则从 获取,获取到后设置到上下文
46 | ///
47 | ///
48 | ///
49 | protected T GetScopeWithContext() where T:notnull
50 | {
51 | var context = GetContext();
52 | if (context is null)
53 | {
54 | context = ServiceProvider.GetRequiredService();
55 | SetContext(context);
56 | }
57 |
58 | return context;
59 | }
60 |
61 | protected T GetEnsureContext() => CurrentContext.GetEnsureContext();
62 | protected T? GetContext() => CurrentContext.GetContext();
63 | protected void SetContext(T context) => CurrentContext.SetContext(context);
64 |
65 | public async ValueTask RunAsync()
66 | {
67 | ThrowNotManager();
68 |
69 | var result = await Manager.RunWorker(this);
70 | return result;
71 | }
72 |
73 | public async ValueTask OnDisposeAsync(IWorkerContext context)
74 | {
75 | if (_onDispose != null)
76 | {
77 | await _onDispose.Invoke(context);
78 | }
79 | await OnDisposeInnerAsync(context);
80 | }
81 |
82 | protected virtual ValueTask OnDisposeInnerAsync(IWorkerContext context)
83 | {
84 | return ValueTask.CompletedTask;
85 | }
86 |
87 | ///
88 | /// 注册释放的执行内容
89 | ///
90 | ///
91 | protected void RegisterOnDispose(Action action)
92 | {
93 | _onDispose += _ =>
94 | {
95 | action();
96 | return ValueTask.CompletedTask;
97 | };
98 | }
99 |
100 | ///
101 | /// 注册释放的执行内容
102 | ///
103 | ///
104 | protected void RegisterOnDispose(Action action)
105 | {
106 | _onDispose += context =>
107 | {
108 | action(context);
109 | return ValueTask.CompletedTask;
110 | };
111 | }
112 |
113 | protected void RegisterOnDispose(Func onDispose) => _onDispose += onDispose;
114 |
115 | private Func? _onDispose;
116 |
117 | void IMessageWorkerManagerSensitive.SetMessageWorkerManager(MessageWorkerManager manager)
118 | {
119 | Manager = manager;
120 |
121 | Logger = ServiceProvider.GetRequiredService>();
122 | }
123 |
124 | protected void ThrowNotManager()
125 | {
126 | if (Manager == null)
127 | {
128 | throw new InvalidOperationException($"MessageWorkerManager is null. 没有注入 MessageWorkerManager 对象,请确保 {GetType().FullName} 在 MessageWorkerManager 里运行。如调用 {nameof(MessageWorkerManager.GetWorker)} 或 {nameof(MessageWorkerManager.RunWorker)} 执行");
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------