WF 从入门到精通(第七章):基本活动的操作

学习完本章,你将掌握:

1.知道怎样使用Sequence 活动

2.知道怎样使用Code 活动

3.知道在工作流中怎样抛出异常并对其进行处理

4.知道如何在代码中暂停和终止你的工作流实例


在本章,我们将正式引入前面已经看到过的一组活动:Sequence 活动和Code 活动。但我相信,适当的错误处理对于精心设计和运行良好 的软件是至关重要的,所以我们将会研究如何使用工作流中的活动抛出异常、捕获异常、甚至暂停和终止你的工作流。我们就从Sequence 活动开始吧。


使用顺序活动对象


实际上,说我们已见过Sequence 活动并不完全正确。我们创建工作流应用程序时实际上使用的是SequentialWorkflow 活 动,但大体的意思是一样的:这个活动包含其它依次要执行的活动。这一点可和使用parallel 活动的并行执行相对比,在第11 章(“Parallel 活 动”)中我们将看到parallel 活动。

当你以特定的顺序执行任务时,你必须依次完成这些任务,这点通常是必须的。

Sequence 活动是一个组合活动,我们在第四章(“活动和工作流类型介绍”)中已经简要讨论过。它包含其它活动,这些活动一定要按次序执行。你可在父Sequence 活动内放入包含parallel 活动在内的其它组合活动。但子活动要依次地,一个接一个地执行,即使这些子活动本身包含的并行执行流也如此。

我 们就来使用Sequence 活动创建一个简单的工作流。我们将再次使用Code 活动,关于它的更详细的细节将在下一节“使用Code 活动”进行讨论。为对 特定的工作流活动的行为进行了解,我们将回到基于控制台的应用程序中。对于基于控制台的应用程序,通常你需要书写的代码更少,因为你不用对用户界面进行处 理。(但随着本书的进展,我们也会创建其它的图形化的测试案例。)


创建一个使用了Sequence活动的工作流

1.下载本章的源代码,本例的最终版本在 “Sequencer Completed”目录下,可使用Visual Studio 2008 打开并直接查看它的运行结果。“Sequencer”目录下则为练习版本,我们将从该版本开始本例的学习,首先使用Visual Studio 2008 打开该解决方案。

2.在我们的解决方案中添加一个顺序工作流库的项目,项目名称为“SequencerFlow”。

3.从工具箱中拖拽一个Sequence 活动到Visual Studio 的工作流视图设计器上。

image.png

4.然后,从工具箱中拖拽一个Code 活动到我们刚添加的Sequence 活动中。

image.png

5.在该活动的ExecuteCode 属性中输入“DoTaskOne”,然后按下回车键。

image.png

6.Visual Studio 会自动把我们带到代码编辑状态。定位到Visual Studio 刚刚添加的DoTaskOne 方法,然后再该方法内输入下面的代码:

Console.WriteLine("Executing Task One...");

7.反复执行步骤4、5、6 两次,添加方法“DoTaskTwo”和“DoTaskThree”,然后在这些方法中修改Console.WriteLine 输出的内容(“One”依次改为“Two”、“Three”)。该工作流的视图设计器现如下图所示:


image.png

8.回到主应用程序,打开Program.cs 文件,定位到Main 方法上。在该方法中,找到下面的代码:

Console.WriteLine("Waiting for workflow completion.");

9.在你找到的这行代码下添加下面的代码:

WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(SequencerFlow.Workflow1));
instance.Start();

10.当然,我们需要在主应用程序项目中引用该SequencerFlow 工作流库。

11.编译该应用程序,纠正任何出现的错误。按下F5 或Ctrl+F5 运行该应用程序。设置一个断点或从命令提示符下运行该程序,这样你就能看到输出结果,结果如下:

image.png

正如你从步骤11 看到的,该任务和我们所期望的顺序依次被执行。需记住两方面:

Sequence 活动是一个组合活动(其它活动的容器),其次就是它容纳的活动以顺序依次执行的。


使用Code活动

迄今为止,在本书中我们经常使用的另一个活动是Code 活动。Code 活动就是要让你的工作流执行你所提供的自定义代码。在下一章我们将看到,还有一种方法可调用外部方法。

当你把一个Code 活动放入你的工作流中时,它的ExecuteCode 属性会被设置为工作流运行时将调用的方法的名称。

实际上,当你在刚刚完成的Sequencer 应用程序设置ExecuteCode 属性时,假如你仔细看看Visual Studio 为你插入的代码,它虽不是一个被调用的方法,但也差不多,它其实是一个事件处理,下面是我们插入代码后的DoTaskOne 方法:


private void DoTaskOne(object sender, EventArgs e)
{
    Console.WriteLine("Executing Task One ");
}

正如你看到的,当工作流运行时在执行你的Code 活动时,它会触发一个事件,该事件的名称就是你在ExecuteCode 属性中设置的值。我们将在本书的剩余部分中好好利用这个Code活动。


使用Throw活动


在本书中很早以前就提到过该活动,但我还没有真正加深这个基本的工作流处理模型的概念。因此,我们需能对真实世界中的各种各样的情况进行建 模,这其中就包含我们需要抛出一个异常的情况。假设有些事情在前进的道路上并不平坦,我们的软件并不能为防止抛出异常而处理任何其它任何情况。假如我们乐 意的话,我们可使用C#中的throw 关键字来抛出一个异常,但在工作流中,我们使用一个特别的活动来做这些事,并使用一个特别的活动

来处理这些异常,这 些我们将在下节看到。假如我们使用C#中的throw 关键字的话,工作流运行时会“swallows(淹没)”该异常,并不会给出通知信息。

这 种现象的原因是Throw 活动。当工作流运行时遭遇Throw 活动时,假如没有相关的失败处理操作,工作流运行时将触发 WorkflowTerminated 事件。但请记住,届时,工作流实例会被终止,工作流运行时会被停止。在这时任何更正异常状况的任何尝试都已为时已 晚,我们仅能做的是重启工作流并开始一个新的工作流实例。假如我们想在终止前更早地处理异常,我们需要使用Throw 和FaultHandler 活动组 合。

备注:推荐的练习使用了 Throw 和 FaultHandler 组合而不是单一的 Throw。使用 Throw活动本身等同于在传统应用程序代码中使用没有进行异常处理的C#的throw 关键字。在本节,我们将单独使用Throw 来看看会发生什么。在下 一节,我们将使用Throw 和FaultHandler活动组合来看看他们怎样协同工作。


回到我们关注的Throw 活动,当你拖拽一个 Throw 活动到设计器上后,你可找到两个需进行设置的属性。首先是FaultType 属性,该属性告知Throw 活动将抛出什么类型的异常;

另一个是 Fault 属性,假如此时Throw 活动抛出的异常不为空,它就指示该Throw 活动引发的异常对象。

FaultType 属性不必做大量的解释,它简单地告知工作流实例将抛出的异常类型。我们没有指明的异常由工作流运行时进行处理或者被忽略(假如你想处理的话,也可在以后进行处理)。

但 Fault 属性的背后有什么密码呢?假如设置了该属性的话,它才真正会是Throw 活动所使用异常。假如该属性为空的话,Throw 活动仍旧抛出一个 FaultType 指定的类型的异常,但它是一个新的异常,该异常没有既定的消息(记住,Message 属性为我们提供了一些除它本身的异常类型之外的关 于错误的一些描述)。

假如你想让Throw 活动抛出一个带有详细Message 的异常,你需要使用new 操作符创建该异常的一个实例并把它指定到你绑定的Throw 活动的相同属性上。

我 再以略微不同的语言来表达上述这些。Throw 活动,更具体地说,它的Fault 属性会和你的工作流中所选择的活动(包括root 活动)中的具有同一种异 常类型的一个属性绑定到一起。就是说,假如你有一个抛出类型异常为NullReferenceException 的Throw 活动,你就必须在你的工作流 中的一些活动上提供一个类型为NullReferenceException 的属性以让Throw 活动使用。然后让Throw 活动绑定这些活动的属性,以 便它能使用你用new 操作符产

生的同一个异常。

在这里,我们会写一些代码来进行试验。我们就开始创建一个使用了Throw 活动的小工作流来看看它是怎样工作的。


创建一个使用了Throw活动的工作流


1. 在下载的本章的源代码内有两个名称为“ErrorThrower Completed”和“ErrorThrower”的文件夹,“ErrorThrower Completed”文件夹内为本例的完整代码,“ErrorThrower”文件夹内为本例的练习项目。我们现在就使用Visual Studio 打开“ErrorThrower”文件夹内的项目。

2.打开ErrorThrower 后,向该解决方案添加一个顺序工作流库的项目,名称为ErrorFlow。

3.从工具箱中拖拽一个Code 活动到Visual Studio 工作流视图设计器上,设置该Code活动的ExecuteCode 属性的值为“PreThrow”。

4.然后,从工具箱中拖拽一个Throw 活动到设计器上,位置在上一步添加的Code 活动的下面。


image.png

5.在Throw 活动的属性面板上,选中它的FaultType 属性,然后点击浏览(...)按钮。(以三个点作为text 的按钮通常表示浏览。)

image.png

6.这将出现一个“浏览并选择.NET 类型”对话框。在里面,选择Throw 活动将构建的异常类型。我们输入或选择“System.Exception”,然后点击确定。

image.png

7.选中Throw 活动的Fault 属性,然后点击它浏览(...)按钮。

image.png

备注:未设置 Fault 属性,甚至未设置 FaultType 属性都不会导致编译失败。但是,这将设置一个message 内容为“Fault 属性未设置”的System.Exception 类型的异常。

8. 这将弹出“将Fault 绑定到活动的属性”对话框。因为我们还未添加fault 代码,因此我们点击“绑定到新成员”选项卡,然后在“新成员名称”中输入 WorkflowException,最后点击确定。这就为你的root 活动添加了WorkflowException 属性。

image.png

9.添加第二个Code 活动,设置它的ExecuteCode 属性的值为“PostThrow”。此时的视图设计器界面如下:

image.png

10.现在我们的工作流就创建好了,然后将添加相应的代码。查看Workflow1.cs 的代码,找到前面步骤添加的PreThrow 事件处理程序,添加下面的代码:

Console.WriteLine("Pre-throwing the exception ");
WorkflowException = new Exception(
"This exception thrown for test and evaluation purposes ");

11.同样,找到PostThrow 事件处理程序并添加下面的代码:

Console.WriteLine("Post-throwing the exception (You won't see this output!)");

12.工作流现在就设计完成了,我们现在回到主应用程序继续工作。打开Program.cs 文件,定位到Main 方法。在Main 方法内,找到下面的代码:

// Print banner.
Console.WriteLine("Waiting for workflow completion.");

13.在上面的代码下,添加下面的代码:

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(ErrorFlow.Workflow1));
instance.Start();

14.现在,为主应用程序添加对ErrorFlow 工作流库的项目引用。

15.编译应用程序,纠正任何编译错误。按下F5 或Ctrl+F5 运行该应用程序。你将看到下面的结果:

image.png

假 如你仔细看看输出结果,你将看到WorkflowTermination 事件处理会被调用,并为我们显示了终止的原因:有一个异常。该异常的 Message 和我们在步骤10 中添加的类型为Exception 的WorkflowException 异常的相关信息相匹配。

备注:当你像在步骤 8 中一样添加新的属性时,这些由 Visual Studio 插入的属性就是依赖属性(看看第四章)。

现在我们看了在WF 中异常是怎样构建的,我们又怎么捕获处理它们呢?毕竟,在工作流的终止事件处理程序中处理异常的话通常都太迟,对于我们没有任何价值。还好,WF 为我们提供了FaultHandler 活动,我们现在就来看看。


使用FaultHandler活动


使 用FaultHandler 的方式和我们目前为止看到过的其它任何活动的使用方式有细小的差别。其实,我们更应该仔细地去看看视图设计器。为什么呢?因为 相比其它的工作流活动,错误处理有一个单独的设计界面(其实,还有第三个设计界面,它为取消处理服务,我们也将在此看到)。

备注:在第 15 章(“工作流和事务”)中,我们将看到补偿活动,它包含补偿事务。处理错误就是其中的一部分。补偿的意思是,产生一个动作,以减轻异常可能带来的危害。


快速浏览工作流视图设计器


在 此时,假如你创建过我给出的工作流实例的话,你可能会对拖拽活动到工作流视图设计器上,然后设置它们的属性,然后编译并执行基本工作流的代码的这一系列的 工作方式感到满意。然而,我还有一些东西没有告诉你,我保留它们的原因是因为我们此前的焦点是放在工作流程序的编写和执行上。

但现在,你已经使用过工作流中的设计工具,体会过工作流的编写,Visual Studio 的使用。我们就花点时间来看看Visual Studio 提供的在工作流辅助设计方面的其它东西,这主要有两个,简要的说就是:附加的视图设计器界面和调试。


附加的视图设计器界面


假如你回头看看一至六章,你会看到那些我们从工具箱拖拽活动到Visual Studio 为我们呈现的视图设计器界面上的例子。但你注意到视图设计器窗口右键快捷菜单中的“查看工作流”、“查看取消处理程序”、“查看错误处理程序”三个菜单项没有?如下图所示:


image.png

图7-1 视图设计器界面右键快捷菜单


“查看工作流”菜单激活目前本书中我们已经使用过的默认的工作流视图编辑界面。

“查看取消处理程序”激活到另一个工作流取消视图,我们可在其中为“取消”处理程序书写代码(见图7-2)。“查看错误处理程序”激活到工作流异常视图(见图7-3)。

image.png

图7-2 工作流取消视图设计器界面

image.png

图7-3 工作流异常视图设计器界面

你随时可能需要通过一些活动名称下面的智能标记来访问附加设计界面,如图7-4。但有些活动,像EventHandlingScope 活动(第十章),你还可访问到更多的界面。

image.png

图7-4 通过智能标记进行界面的切换


没有什么奇怪的,你拖拽到取消设计界面上的工作流活动在该工作流实例被取消时执行。

这使你有机会在工作流实例真正停止执行前去执行一些清理或通知的任务。

错 误处理设计界面可容纳许多的错误处理。每一个错误处理可处理一种,也仅能处理一种异常类型。组合活动一般而言都包含错误处理,假如需要的话也允许子活动进 行处理错误而不用把它们发送到父活动中。回头看看图7-3,你可看到外面围着蓝色圆圈的两个箭头按钮。错误处理活动可拖到这两个箭头之间,这些箭头允许你 进行滚动以显示屏幕之外的错误处理活动。箭头按钮下面的区域是另一个和活动相关联的异常处理的工作流设计界面。通常

是拖拽一个Code 活动到这里为你做些 在错误情况下的清理工作或执行其它必要的处理。在更多地了解工作流的调试视图设计界面后我们将对此做一些练习。


调试视图设计器


你或许不知道你可在工作流的视图设计器上设置断点。其实,在你的工作流中你还能一个活动一个活动地单步执行(而不是在你的源代码中一行一行地执行)。

在工作流视图设计器上设置断点的方法是,右键单击要设置断点的活动,然后在快捷菜单中依次选择“断点”、“插入断点”,参见下图:

image.png

图7-5 使用工作流视图设计器设置断点


然后工作流视图设计器会在该活动的图形界面中放置一个红色圆球,就像你在代码中某一行上设置断点进行调试时看到的红色圆球一样,参见7-6。你也能移除断点,禁用所有断点等等。


image.png

图7-6 带断点的活动


有了这些知识储备,我们现在就可向我们的工作流添加FaultHandler 活动了。

修改我们的工作流,以便使用FaultHandler活动

1.在Visual Studio 中打开ErrorThrower 应用程序,选择ErrorFlow 项目,选中Workflow1.cs 文件,点击视图设计器,激活到工作流视图设计器界面上,我们需要修改些东西。

2.通过右键快捷菜单(“查看错误处理程序”)切换到工作流异常视图设计器界面,如下图:

image.png

3.从工具箱中选中FaultHandler 活动,拖拽到工作流设计器的异常处理界面上,然后放到两个蓝色按钮之间。现在你的视图设计器显示效果如下所示:

image.png

4.我们需设置一些属性,以使错误处理全面投入运作。我们首先要设置的是FaultType属性。在Visual Studio 的属性面板中选中FaultType 属性,点击浏览按钮(该按钮是三个点)激活“浏览并选择.NET 类型”对话框。如下图:

image.png

5.在“浏览并选择.NET 类型”对话框打开后,选择或输入“System.Exception”类型名称,点击确定关闭对话框,你会发现FaultType 属性的值已被设置为“System.Exception”了。

image.png

image.png

备注: 我们设置FaultHandler 活动使用的异常类型和我们本章前面使用 Throw 活动时设置的异常类型是相同的,但这不是巧合。它们的设置要匹配。假如 在你的工作流中没有为一个Throw 活动进行相应的异常处理,那请牢记,假如运行时抛出异常,你的工作流将很快就执行到 WorkflowTerminated 事件。假如你不想这样,就应添加恰当(属性)的FaultHandler活动。

备注:尽管在前面的图片中我们看到了 Fault 属性,但它实际上是禁用的,因此不能设置,我们忽略它。


6. 迄今为止,我们添加了FaultHandler 活动,并设置了它将处理的异常类型,但我们

其实还未写入任何代码来处理可能抛出的异常。因此,我们要从工具 箱中拖拽一个Code 到我们添加的FaultHandler 活动的下方区域中,该区域以“faultHandlerActivity1”命名,它看起来就 像是一个微型的工作流视图设计器。我们拖入的Code 活动的ExecuteCode 属性设置为“OnException”,然后按下回车键。

7.然后Visual Studio 将切换到代码视图中,找到Visual Studio 刚添加的OnException事件处理并为其添加以下代码:

Console.WriteLine("Exception handled within the workflow! The exception was: '{0}'",
WorkflowException != null ? WorkflowException.Message :
"Exception property not set, generic exception thrown");

8.现在编译并执行代码,你将看到下面的执行结果:

image.png

备注: 在这种层次上抛出和处理异常,你的工作流实质上仍旧会被停止。这样做的优点是你的工作流能带着异常工作而不是把异常抛出给工作流运行时处理。假如你想在特 定的异常抛出后还能继续进行处理,你就不要用Throw 活动和FaultHandler 活动来处理它们。相反,你要使用try/catch 来包围活动代 码,以便异常绝不会传给运行时处置。假如你不能充分地在(try/catch)内部处理异常,那只能求助于Throw 活动和FaultHandler 活 动。


使用Suspend活动


另一个在特定条件下会对你有用的活动是Suspend 活动。事实上,它常见的使用场景是使用FaultHandler 活动处理错误后,再使用Suspend 活动进行暂停,然后发送需要人为干预的信号。

使 用Suspend 活动时,你需要为该活动的Error 属性提供一个字符串。这个属性可以绑定在一个依赖属性上(这一点像Throw 活动)、一个类的属性或 字段、甚至是一个文本字符串(本例中我们将这样做)。当Suspend 活动执行时,工作流运行时会触发WorkflowSuspended事件,传给该事 件的argument 参数中将带有该error 字符串。

使一个工作流实例处于暂停状态的含义是,该实例当前不再执行,但它也不被卸载。本质上它维持这种形式,等待一些动作。它也不被认为是空闲状态,因此自动的持久化对它也不起作用。使用Suspend 活动相对简单,下面你就将看到。


备注:在你的基于工作流的应用程序中处理 WorkflowSuspended 事件是一个好主意,这使工作流实例进入暂停状态后为你提供一个动作。至少你能得到工作流实例已经被暂停了的通知,你可移除、恢复或者重新启动这些工作流实例。


修改我们的工作流,以便使用Suspend活动


1. 下载本章源代码,在Visual Studio 中打开ErrorSuspender 应用程序,选中ErrorFlow项目中的Workflow.cs 文件,打开该文件的工作流视图设计器界 面。选择工作流异常的设计界面,我们将为System.Exception 错误处理程序添加Suspend 活动。

2.从工具箱中拖拽一个Suspend 活动到错误处理程序界面上,并把它放到Code 活动的下面,如下图所示:

image.png

3.设置该Suspend 活动的Error 属性为“This is an example suspension error...”

image.png

提示:我们在这里输入的是一个文本字符串,但是,你也能把它绑定到一个字符串类型的依赖属性,这样当你的工作流执行时可更容易地对它的值进行设置。方法是单击浏览按钮(三个点的按钮),打开“将Error 绑定到活动的属性”对话框,然后你就可对它要绑定的属性进行选择。

4.因为在我们的主应用程序中没有WorkflowSuspended 事件处理程序,因此我们需要对主应用程序的Program.cs 文件进行编辑。在合适的位置添加下面的代码:

workflowRuntime.WorkflowSuspended += new EventHandler<WorkflowSuspendedEventArgs>(workflowSuspen
ded);

5.因为我们使用了名称为WorkflowSuspended 的事件处理程序,因此我们需要实现该事件处理程序,代码如下:

static void workflowSuspended(object sender, WorkflowSuspendedEventArgs e)
{
    Console.WriteLine("Workflow instance suspended, error: '{0}'.", e.Error);
    waitHandle.Set();
}

6.编译该应用程序,然后按F5 或Ctrl+F5 执行该程序。该程序的输出结果如下:

image.png

运 行该应用程序时,你会在控制台中看到主应用程序中的WorkflowSuspended 事件处理程序产生的输出结果。但你能做更多的工作,而不是仅仅向控 制台输出一串文本。你能为你的业务处理工作流产生任何其它动作。尽管在这里你可能恢复该工作流的处理过程,但通常并不建议这样做。一是全部正处理的活动将 会被跳过,保留你的工作流实例以进行处理过程的恢复是后面阶段要做的事,这可能不是好事情(跳过了那些步骤,你又怎么说明它的原因

呢?)。但是至少,你能 从处理进程中干净地移除该工作流实例,并可使用任何必要的清理代码。

似乎异常和暂停工作流实例都有不足,因此,假如你需要的话,你可这样做,那就是终止你的工作流实例。让我们来看看怎么做。


使用Terminate活动


有些时候事情会变得很糟糕,例如你没有资源,需要结束某个工作流实例;也许从外部进程中返回的一些数据的格式或者计算结果是错误的;或者数据库服务器出现问题,没有它你就不能前进等等。

WF 为我们提供了一个现成的方式来终止我们的工作流,那就是使用Terminate 活动。

Terminate 活动的使用方法和Suspend 活动完全相同,事 实上它们的属性也是相同的。不同之处在于,当Terminate 执行时,所有期望你的工作流实例要继续执行的事情都将丢失。

当 Terminate 执行时,工作流运行时触发WorkflowTerminated 事件,这正像有一个未处理的异常一样。当处理 WorkflowTerminated 事件时获取两个不同方面的信息是困难的,所有你能做的实际上就是检查 WorkflowTerminatedEventArgs 参数,看看它的Exception 属性。假如该工作流实例是使用Terminate 活动终止的, 该异常类型将会是System.Workflow.ComponentModel.WorkflowTerminatedException 而不会是其它 (甚至是

更加常见)的异常类型。

我们就来看看在我们的工作流代码中怎样使用Terminate 活动。


修改我们的工作流,以便使用Terminate活动


1.下载本章源代码,用 Visual Studio 打开ErrorTerminator 文件夹中的解决方案(ErrorTerminator Completed 文件夹中为本例的最终源代码)。选中ErrorFlow 项目中的Workflow1.cs 文件,打开它的工作流视图设计器界面。

2.在错误处理程序的设计界面上删除已存在的Suspend 活动,然后从工具箱中拖拽一个Terminate 活动到错误处理程序的设计界面上,把该活动放在Code 活动的下面。

image.png

3.在放好该Terminate 活动后,设置它的Error 属性为“This is an example terminationerror...”字符串。

备注:再重复一遍,你可像我们现在做的一样,设置该属性为一个文本字符串,但你也能把该属性绑定到某个活动的字段、属性或者依赖属性上。

image.png

4.编译该应用程序,修正所有的编译错误,然后按下F5 或者Ctrl+F5 运行该应用程序,你将看到下面的运行结果:

image.png

Terminate 活动和Suspend 活动一样,都是相当简单的活动,但它很强大。你通常不会需要它,但当你的工作流出现问题不能继续运行时,Terminate 活动就是工具箱中最好的工具。

源码下载 http://files.cnblogs.com/gyche/WF%20Step%20by%20Step/Chapter7.rar



--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!



本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年3月    »
123
45678910
11121314151617
18192021222324
25262728293031
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864