C# 从做早餐看同步异步


概述

一天之计在于晨,每天的早餐也是必不可少,但是很多人为了节约时间,都是简单的吃点凑合一下或干脆不吃早餐,这对于个人身体和工作效率来说,无疑是不合理的,那么要如何做一顿早餐呢?如何能节约做早餐的时间呢?本文以一个简单的小例子,简述如何做一顿早餐及如何优化做早餐的时间。仅供学习分享使用,如有不足之处,还请指正。

正常情况下,做早餐可以分为以下几个步骤:

  1. 倒一杯咖啡。
  2. 加热平底锅,然后煎两个鸡蛋。
  3. 煎三片培根。
  4. 烤两片面包。
  5. 在烤面包上加黄油和果酱。
  6. 倒一杯橙汁。

同步方式做早餐

根据以上步骤进行编程,做一份早餐需要编写程序如下:

  1         /// <summary>
  2         /// 同步做早餐
  3         /// </summary>
  4         /// <param name="sender"></param>
  5         /// <param name="e"></param>
  6         private void btnBreakfast_Click(object sender, EventArgs e)
  7         {
  8             this.txtInfo.Clear();
  9             Stopwatch watch = Stopwatch.StartNew();
 10             watch.Start();
 11             //1. 倒一杯咖啡。
 12             string cup = PourCoffee();
 13             PrintInfo("咖啡冲好了");
 14             //2. 加热平底锅,然后煎两个鸡蛋。 
 15             string eggs = FryEggs(2);
 16             PrintInfo("鸡蛋煎好了");
 17             //3. 煎三片培根。 
 18             string bacon = FryBacon(3);
 19             PrintInfo("培根煎好了");
 20             //4. 烤两片面包。 
 21             string toast = ToastBread(2);
 22             //5. 在烤面包上加黄油和果酱。
 23             ApplyButter(toast);
 24             ApplyJam(toast);
 25             PrintInfo("面包烤好了");
 26             //6. 倒一杯橙汁。
 27             string oj = PourOJ();
 28             PrintInfo("橙汁倒好了");
 29             PrintInfo("早餐准备完毕!");
 30             watch.Stop();
 31             TimeSpan time = watch.Elapsed;
 32             PrintInfo(string.Format("总运行时间为:{0}秒", time.TotalSeconds.ToString("0.00")));
 33         }
 34 
 35         /// <summary>
 36         /// 倒一杯咖啡
 37         /// </summary>
 38         /// <returns></returns>
 39         private string PourCoffee()
 40         {
 41             PrintInfo("正在冲咖啡...");
 42             return "咖啡";
 43         }
 44 
 45         /// <summary>
 46         /// 抹果酱
 47         /// </summary>
 48         /// <param name="toast"></param>
 49         private void ApplyJam(string toast) =>
 50             PrintInfo("往面包抹果酱");
 51 
 52         /// <summary>
 53         /// 抹黄油
 54         /// </summary>
 55         /// <param name="toast"></param>
 56         private void ApplyButter(string toast) =>
 57             PrintInfo("往面包抹黄油");
 58 
 59         /// <summary>
 60         /// 烤面包
 61         /// </summary>
 62         /// <param name="slices"></param>
 63         /// <returns></returns>
 64         private string ToastBread(int slices)
 65         {
 66             for (int slice = 0; slice < slices; slice++)
 67             {
 68                 PrintInfo("往烤箱里面放面包");
 69             }
 70             PrintInfo("开始烤...");
 71             Task.Delay(3000).Wait();
 72             PrintInfo("从烤箱取出面包");
 73 
 74             return "烤面包";
 75         }
 76 
 77         /// <summary>
 78         /// 煎培根
 79         /// </summary>
 80         /// <param name="slices"></param>
 81         /// <returns></returns>
 82         private string FryBacon(int slices)
 83         {
 84             PrintInfo($"放 {slices} 片培根在平底锅");
 85             PrintInfo("煎第一片培根...");
 86             Task.Delay(3000).Wait();
 87             for (int slice = 0; slice < slices; slice++)
 88             {
 89                 PrintInfo("翻转培根");
 90             }
 91             PrintInfo("煎第二片培根...");
 92             Task.Delay(3000).Wait();
 93             PrintInfo("把培根放盘子里");
 94 
 95             return "煎培根";
 96         }
 97 
 98         /// <summary>
 99         /// 煎鸡蛋
100         /// </summary>
101         /// <param name="howMany"></param>
102         /// <returns></returns>
103         private string FryEggs(int howMany)
104         {
105             PrintInfo("加热平底锅...");
106             Task.Delay(3000).Wait();
107             PrintInfo($"磕开 {howMany} 个鸡蛋");
108             PrintInfo("煎鸡蛋 ...");
109             Task.Delay(3000).Wait();
110             PrintInfo("鸡蛋放盘子里");
111 
112             return "煎鸡蛋";
113         }
114 
115         /// <summary>
116         /// 倒橙汁
117         /// </summary>
118         /// <returns></returns>
119         private string PourOJ()
120         {
121             PrintInfo("倒一杯橙汁");
122             return "橙汁";
123         }

同步做早餐示例

通过运行示例,发现采用同步方式进行编程,做一份早餐,共计15秒钟,且在此15秒钟时间内,程序处于【卡住】状态,无法进行其他操作。如下所示:

同步做早餐示意图

同步方式做早餐,就是一个做完,再进行下一个,顺序执行,如下所示:

同步方式为何会【卡住】?

因为在程序进程中,会有一个主线程,用于响应用户的操作,同步方式下,做早餐的和前端页面同在主线程中,所以当开始做早餐时,就不能响应其他的操作了。这就是【两耳不闻窗外事,一心只读圣贤书】的境界。但如果让用户长时间处于等待状态,会让用户体验很不友好。比如,刘玄德三顾茅庐,大雪纷飞之下,诸葛亮在草庐中午睡,刘关张在大雪中静等。试问有几人会有玄德的耐心,何况程序也不是诸葛亮,用户也没有玄德的耐心!

异步方式做早餐

上述代码演示了不正确的实践:构造同步代码来执行异步操作。 顾名思义,此代码将阻止执行这段代码的线程执行任何其他操作。 在任何任务进行过程中,此代码也不会被中断。 就如同你将面包放进烤面包机后盯着此烤面包机一样。 你会无视任何跟你说话的人,直到面包弹出。如何做才能避免线程阻塞呢?答案就是异步。 await 关键字提供了一种非阻塞方式来启动任务,然后在此任务完成时继续执行。

首先更新代码,对于耗时的程序,采用异步方式做早餐,如下所示:

 1         private async void btnBreakfastAsync_Click(object sender, EventArgs e)
 2         {
 3             this.txtInfo.Clear();
 4             Stopwatch watch = Stopwatch.StartNew();
 5             watch.Start();
 6             //1. 倒一杯咖啡。
 7             string cup = PourCoffee();
 8             PrintInfo("咖啡冲好了");
 9             //2. 加热平底锅,然后煎两个鸡蛋。 
10             //Task<string> eggs = FryEggsAsync(2);
11             string eggs =await FryEggsAsync(2);
12             PrintInfo("鸡蛋煎好了");
13             //3. 煎三片培根。 
14             string bacon =await FryBaconAsync(3);
15             PrintInfo("培根煎好了");
16             //4. 烤两片面包。 
17             string toast =await ToastBreadAsync(2);
18             //5. 在烤面包上加黄油和果酱。
19             ApplyButter(toast);
20             ApplyJam(toast);
21             PrintInfo("面包烤好了");
22             //6. 倒一杯橙汁。
23             string oj = PourOJ();
24             PrintInfo("橙汁倒好了");
25             PrintInfo("早餐准备完毕!");
26             watch.Stop();
27             TimeSpan time = watch.Elapsed;
28             PrintInfo(string.Format("总运行时间为:{0}秒", time.TotalSeconds.ToString("0.00")));
29         }
30 
31         /// <summary>
32         /// 异步烤面包
33         /// </summary>
34         /// <param name="slices"></param>
35         /// <returns></returns>
36         private async Task<string> ToastBreadAsync(int slices)
37         {
38             for (int slice = 0; slice < slices; slice++)
39             {
40                 PrintInfo("往烤箱里面放面包");
41             }
42             PrintInfo("开始烤...");
43             await Task.Delay(3000);
44             PrintInfo("从烤箱取出面包");
45 
46             return "烤面包";
47         }
48 
49         /// <summary>
50         /// 异步煎培根
51         /// </summary>
52         /// <param name="slices"></param>
53         /// <returns></returns>
54         private async Task<string> FryBaconAsync(int slices)
55         {
56             PrintInfo($"放 {slices} 片培根在平底锅");
57             PrintInfo("煎第一片培根...");
58             await Task.Delay(3000);
59             for (int slice = 0; slice < slices; slice++)
60             {
61                 PrintInfo("翻转培根");
62             }
63             PrintInfo("煎第二片培根...");
64             await Task.Delay(3000);
65             PrintInfo("把培根放盘子里");
66 
67             return "煎培根";
68         }
69 
70         /// <summary>
71         /// 异步煎鸡蛋
72         /// </summary>
73         /// <param name="howMany"></param>
74         /// <returns></returns>
75         private async Task<string> FryEggsAsync(int howMany)
76         {
77             PrintInfo("加热平底锅...");
78             await Task.Delay(3000);
79             PrintInfo($"磕开 {howMany} 个鸡蛋");
80             PrintInfo("煎鸡蛋 ...");
81             await Task.Delay(3000);
82             PrintInfo("鸡蛋放盘子里");
83 
84             return "煎鸡蛋";
85         }

 注意:通过测试发现,异步方式和同步方式的执行时间一致,所以采用异步方式并不会缩短时间,但是程序已不再阻塞,可以同时响应用户的其他请求

优化异步做早餐

通过上述异步方式,虽然优化了程序,不再阻塞,但是时间并没有缩短,那么要如何优化程序来缩短时间,以便早早的吃上可口的早餐呢?答案就是在开始一个任务后,在等待任务完成时,可以继续进行准备其他的任务。 你也几乎将在同一时间完成所有工作。 你将吃到一顿热气腾腾的早餐。通过合并任务和调整任务的顺序,将大大节约任务的完成时间,如下所示:

 1         /// <summary>
 2         /// 优化异步做早餐
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private async void btnBreakfast2_Click(object sender, EventArgs e)
 7         {
 8             this.txtInfo.Clear();
 9             Stopwatch watch = Stopwatch.StartNew();
10             watch.Start();
11             //1. 倒一杯咖啡。
12             string cup = PourCoffee();
13             PrintInfo("咖啡冲好了");
14             //2. 加热平底锅,然后煎两个鸡蛋。 
15             Task<string> eggsTask = FryEggsAsync(2);
16             //3. 煎三片培根。 
17             Task<string> baconTask = FryBaconAsync(3);
18             //4.5合起来 烤面包,抹果酱,黄油
19             Task<string> toastTask = MakeToastWithButterAndJamAsync(2);
20 
21             string eggs = await eggsTask;
22             PrintInfo("鸡蛋煎好了");
23 
24             string bacon = await baconTask;
25             PrintInfo("培根煎好了");
26 
27             string toast = await toastTask;
28             PrintInfo("面包烤好了");
29             //6. 倒一杯橙汁。
30             string oj = PourOJ();
31             PrintInfo("橙汁倒好了");
32             PrintInfo("早餐准备完毕!");
33             watch.Stop();
34             TimeSpan time = watch.Elapsed;
35             PrintInfo(string.Format("总运行时间为:{0}秒", time.TotalSeconds.ToString("0.00")));
36         }
37 
38         /// <summary>
39         /// 组合任务
40         /// </summary>
41         /// <param name="number"></param>
42         /// <returns></returns>
43         private async Task<string> MakeToastWithButterAndJamAsync(int number)
44         {
45             var toast = await ToastBreadAsync(number);
46             ApplyButter(toast);
47             ApplyJam(toast);
48             return toast;
49         }

在本例中,合并了【烤面包+抹果酱+抹黄油】为一个任务,这样是烤面包的同时,可以煎鸡蛋,煎培根,三项耗时任务同时执行。在三个任务都完成是,早餐也就做好了,示例如下所示:

通过以上优化示例发现,通过合并任务和调整顺序,做一份早餐,需要6.06秒。

优化异步早餐示意图

优化后的异步做早餐,由于一些任务并发运行,因此节约了时间。示意图如下所示:

异步异常

上述示例假定所有的任务都可以正常完成,那么如果某一个任务执行过程中发生了异常,要如何捕获呢?答案是:当任务无法成功完成时,它们将引发异常。 当启动的任务为 awaited 时,客户端代码可捕获这些异常。

例如当烤面包的时候,烤箱突然着火了,如何处理异常呢?代码如下所示:

 1         private async void btnBreakfastAsync3_Click(object sender, EventArgs e)
 2         {
 3             try
 4             {
 5                 this.txtInfo.Clear();
 6                 Stopwatch watch = Stopwatch.StartNew();
 7                 watch.Start();
 8                 //1. 倒一杯咖啡。
 9                 string cup = PourCoffee();
10                 PrintInfo("咖啡冲好了");
11                 //2. 加热平底锅,然后煎两个鸡蛋。 
12                 Task<string> eggsTask = FryEggsAsync(2);
13                 //3. 煎三片培根。 
14                 Task<string> baconTask = FryBaconAsync(3);
15                 //4.5合起来 烤面包,抹果酱,黄油
16                 Task<string> toastTask = MakeToastWithButterAndJamAsyncEx(2);
17 
18                 string eggs = await eggsTask;
19                 PrintInfo("鸡蛋煎好了");
20 
21                 string bacon = await baconTask;
22                 PrintInfo("培根煎好了");
23 
24                 string toast = await toastTask;
25                 PrintInfo("面包烤好了");
26                 //6. 倒一杯橙汁。
27                 string oj = PourOJ();
28                 PrintInfo("橙汁倒好了");
29                 PrintInfo("早餐准备完毕!");
30                 watch.Stop();
31                 TimeSpan time = watch.Elapsed;
32                 PrintInfo(string.Format("总运行时间为:{0}秒", time.TotalSeconds.ToString("0.00")));
33             }
34             catch (AggregateException ex) {
35                 PrintInfo("线程内部异常");
36                 PrintInfo(ex.StackTrace);
37             }
38             catch (Exception ex)
39             {
40                 PrintInfo("其他异常");
41                 PrintInfo(ex.Message);
42             }
43         }
44 
45         /// <summary>
46         /// 组合任务
47         /// </summary>
48         /// <param name="number"></param>
49         /// <returns></returns>
50         private async Task<string> MakeToastWithButterAndJamAsyncEx(int number)
51         {
52             var toast = await ToastBreadAsyncEx(number);
53             ApplyButter(toast);
54             ApplyJam(toast);
55             return toast;
56         }
57 
58         /// <summary>
59         /// 异步烤面包异常
60         /// </summary>
61         /// <param name="slices"></param>
62         /// <returns></returns>
63         private async Task<string> ToastBreadAsyncEx(int slices)
64         {
65             for (int slice = 0; slice < slices; slice++)
66             {
67                 PrintInfo("往烤箱里面放面包");
68             }
69             PrintInfo("开始烤...");
70             await Task.Delay(2000);
71             PrintInfo("着火了! 面包糊了!");
72             int a = 1, b = 0;
73             int i = a / b;//制造一个异常
74             //throw new InvalidOperationException("烤箱着火了!");
75             await Task.Delay(1000);
76             PrintInfo("从烤箱取出面包");
77 
78             return "烤面包";
79         }

异步任务异常示例

请注意,从烤面包机着火到发现异常,有相当多的任务要完成。 当异步运行的任务引发异常时,该任务出错。 Task 对象包含 Task.Exception 属性中引发的异常。 出错的任务在等待时引发异常。

需要理解两个重要机制:异常在出错的任务中的存储方式,以及在代码等待出错的任务时解包并重新引发异常的方式。

当异步运行的代码引发异常时,该异常存储在 Task 中。 Task.Exception 属性为 System.AggregateException,因为异步工作期间可能会引发多个异常。 引发的任何异常都将添加到 AggregateException.InnerExceptions 集合中。 如果该 Exception 属性为 NULL,则将创建一个新的 AggregateException 且引发的异常是该集合中的第一项。

对于出错的任务,最常见的情况是 Exception 属性只包含一个异常。 当代码 awaits 出错的任务时,将重新引发 AggregateException.InnerExceptions 集合中的第一个异常。 因此,此示例的输出显示 InvalidOperationException 而不是 AggregateException。 提取第一个内部异常使得使用异步方法与使用其对应的同步方法尽可能相似。 当你的场景可能生成多个异常时,可在代码中检查 Exception 属性。

高效的等待

通过以上示例,需要等待很多任务完成,然后早餐才算做好,那么如何才能高效优雅的等待呢?可以通过使用 Task 类的方法改进上述代码末尾的一系列 await 语句。其中一个 API 是 WhenAll,它将返回一个其参数列表中的所有任务都已完成时才完成的 Task,如下所示:

 1         private async void btnBreakfastAsync4_Click(object sender, EventArgs e)
 2         {
 3             this.txtInfo.Clear();
 4             Stopwatch watch = Stopwatch.StartNew();
 5             watch.Start();
 6             //1. 倒一杯咖啡。
 7             string cup = PourCoffee();
 8             PrintInfo("咖啡冲好了");
 9             //2. 加热平底锅,然后煎两个鸡蛋。 
10             Task<string> eggsTask = FryEggsAsync(2);
11             //3. 煎三片培根。 
12             Task<string> baconTask = FryBaconAsync(3);
13             //4.5合起来 烤面包,抹果酱,黄油
14             Task<string> toastTask = MakeToastWithButterAndJamAsync(2);
15             //等待任务完成
16             await Task.WhenAll(eggsTask, baconTask, toastTask);
17             
18             PrintInfo("鸡蛋煎好了");
19             PrintInfo("培根煎好了");
20             PrintInfo("面包烤好了");
21             //6. 倒一杯橙汁。
22             string oj = PourOJ();
23             PrintInfo("橙汁倒好了");
24             PrintInfo("早餐准备完毕!");
25             watch.Stop();
26             TimeSpan time = watch.Elapsed;
27             PrintInfo(string.Format("总运行时间为:{0}秒", time.TotalSeconds.ToString("0.00")));
28         }

另一种选择是使用 WhenAny,它将返回一个当其参数完成时才完成的 Task<Task>。如下所示:

 1         private async void btnBreakfastAsync5_Click(object sender, EventArgs e)
 2         {
 3             this.txtInfo.Clear();
 4             Stopwatch watch = Stopwatch.StartNew();
 5             watch.Start();
 6             //1. 倒一杯咖啡。
 7             string cup = PourCoffee();
 8             PrintInfo("咖啡冲好了");
 9             //2. 加热平底锅,然后煎两个鸡蛋。 
10             Task<string> eggsTask = FryEggsAsync(2);
11             //3. 煎三片培根。 
12             Task<string> baconTask = FryBaconAsync(3);
13             //4.5合起来 烤面包,抹果酱,黄油
14             Task<string> toastTask = MakeToastWithButterAndJamAsync(2);
15             //等待任务完成
16             var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
17             while (breakfastTasks.Count > 0)
18             {
19                 Task finishedTask = await Task.WhenAny(breakfastTasks);
20                 if (finishedTask == eggsTask)
21                 {
22                     PrintInfo("鸡蛋煎好了");
23                 }
24                 else if (finishedTask == baconTask)
25                 {
26                     PrintInfo("培根煎好了");
27                 }
28                 else if (finishedTask == toastTask)
29                 {
30                     PrintInfo("面包烤好了");
31                 }
32                 breakfastTasks.Remove(finishedTask);
33             }
34             //6. 倒一杯橙汁。
35             string oj = PourOJ();
36             PrintInfo("橙汁倒好了");
37             PrintInfo("早餐准备完毕!");
38             watch.Stop();
39             TimeSpan time = watch.Elapsed;
40             PrintInfo(string.Format("总运行时间为:{0}秒", time.TotalSeconds.ToString("0.00")));
41         }

以上就是由同步到异步再到优化异步任务的逐步过程,旨在抛砖引玉,一起学习,共同进步。

备注

黄鹤楼【作者】崔颢 【朝代】唐

昔人已乘黄鹤去,此地空余黄鹤楼。

黄鹤一去不复返,白云千载悠悠。

历历汉阳树,芳草萋萋鹦鹉洲。

日暮乡关何处是?烟波江上使人愁。

文章来源:https://www.cnblogs.com/hsiang/p/15645527.html

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
管理员
上一篇:记一次 .NET 某化妆品 webapi 卡死分析
下一篇:.NET SAAS 架构与设计 -SqlSugar ORM
评论列表

发表评论

评论内容
昵称:
验证码:
验证码
关联文章

C# 早餐同步异步
.net异步Task转同步
Javascript 中通过 yield 和 promise 使异步同步
Task 使用详细[基础操作,异步原则,异步函数,异步模式]
使用 mitmproxy + python 拦截代理
SAP 事务码后缀规律:先家族,再最后一个字母
同步数据到外部系统
使用 WPF 个 PowerPoint 系列 文本 BodyProperties 的 FontScale 与文本字号缩放
使用 WPF 个 PowerPoint 系列 基于 OpenXML 解析实现 PPT 文本描边效果
.NET Core 实现动态代理AOP(面向切面编程)
EFCore异步查询报错
化繁为简,用几个例子介绍JavaScript异步处理async awite
两台Linux之间自动同步文件
vue中异步函数async和await的用法
nginx内外网分流+https证书同步
vscode同步配置时,重新生成 github token 之后,怎样继续下载配置
C#的进化——C#发展史、C#1.0-10.0语法系统性梳理、C#与JAVA的对比
C#性能优化总结
EF异步查询ToListAsync报错
SAP S/4HANA 物料类型设置数量与价值更新:小白也能懂的库存与财务联动

热门标签
.NET Core .NET Reactor ag-grid AI发布 api安全 ASP.NET Core C#DLL加密 C#播放声音 C#代码混淆 C#代码加密 ChromeDriver Codex DateTime DBeaver devexpress devTool DLL混淆 edge.js EF EFCore Electron element-ui el-form el-table excel FastReport FileStream FolderBrowerDialog FolderSelectDialog form提交 git gridcontrol gridview input javascript json字符串 JS转换对象JSON jwt JWT授权 linq log Math MCP mitmproxy MVC MySQL Navicat netstat nginx node_modules NSwag Nuget Nuget镜像 number PowerShell pyinstaller python pythoncom python爬虫 python抓包 pywin32 redis Requests-html RestSharp Selenium sql SQL Server Swagger to-cms Visual Studio VSCode vue VueRouter vue路由 VUE页面通讯 Webpack Windows Windows服务 winform wmi xlrd yaml YESCMS YESWEB开发框架 白象 表单提交 播放声音 打开URL 代码混淆 弹窗提醒 端口占用 对象转换 分布式 公共字典 机器码 进程排查 静态资源 开发指南 路由参数 密钥 配置教程 配置文件 权限 人工智能 任务 任务调度 日期间隔 日志 日志记录 省市区 授权验证 数据库 四舍五入 文案 文件读取 文件夹选择 文件目录选择 问题排查 行政区域数据 页面通讯 中间件 CSharp 事务锁 工单系统 并发控制 重复提交 CMS Markdig Markdown markdown-it marked 技术选型 VS Code 开发工具 源代码管理 版本控制 Docker PostgreSQL 时区 部署排查 CMS架构 EF Core 主题系统 二次开发 插件系统 容器 运维命令 镜像清理 Linux NAS 远程挂载 飞牛 fnOS S/4HANA SAP GUI SAP HANA SAP R/3 SAP入门 SAP版本 ERP SAP SAP MM 库存管理 物料管理 采购管理 入门教程 SAP S/4HANA SPRO 企业结构 采购组织 MM01 物料主数据 物料类型 BP分组 业务伙伴 供应商主数据 ME41 RFQ 库存物料 采购流程 ME51 消耗性物料 科目分配 采购申请 AC03 ML81N 外部服务 服务主数据 Business Partner SAP培训 ME51N MM模块 Lean Services MM-SRV 外部服务采购 PIR 供应来源 采购主数据 采购信息记录 ME31K 框架协议 计划协议 采购合同 ME01 供应来源确定 货源清单 MEQ1 供应源确定 配额安排 配额评分 MD04 MD21 MRP 计划文件 需求计划 批量程序 MD01N MD02 MRP Live MD05 MM 物料计划 优化采购 供应源 采购订单 ME2A 供应商确认 采购监控 Flexible Workflow 凭证释放 采购审批 释放策略 实地盘点 物料凭证 货物移动 MIGO 收货 移动类型 已撤回 供应商退货 货物发出 STO 库存转储 转移过账 生产订单 预留 GR/IR MIRO 供应商发票 物流发票校验 OMR2 税码 FI PP SD 实操教程 MRBR OMR6 发票差异 交货成本 后续借记 MI01 实物盘点 盘点差异 公司代码 工厂 组织结构 OMS2 主数据定制 自动科目确定 BP角色 CVI 伙伴确定 编号范围 凭证类型 字段选择 FBN1 OMBT OMC2 会计凭证 OMJJ BOM 委外加工 项目类别L MRKO 供应商寄售 特殊库存K MRKON PIPE Pipeline 特殊库存P ERS MRIS 发票计划 周期性结算 里程碑付款 变更追踪 版本管理 采购凭证 SFTP WebDAV 网盘 飞牛fnOS AMPL HERS MPN 中文教程 库存确定 可用性检查 缺件检查 Output Management 消息确定 输出确定 分割评估 库存计价 评估类别 评估类型 PB00 RM0000 条件技术 采购定价 MM-FI集成 OBYC 库存估价 文本类型 文本采用 EFB EVO MSV SU3 用户参数 发票校验 合同参照 履约保留款 特别总账 预付款 Fiori Launchpad SAP Fiori 应用导航 用户体验 LSMW LTMC Migration Cockpit 数据迁移 BRFplus OPD Output Control My Inbox 审批流程 灵活工作流 SAP PP 外部加工 SAP QM 检验批 质量信息记录 采购收货 SAP PM 维护BOM 维护订单 SAP SD SAP Service 端到端流程 MM模块培训 FI-MM集成 供应商管理 审批配置 FICO入门 SAP FICO 财务配置 供应商税务 预扣税 House Bank 银行对账 客户清账 应收账款 FI控制 验证与替代 印度 GST 税务配置 F110 FBZP EWM入门 SAP EWM 仓库管理 OX14 成本核算 物料评估 后勤配置 物料组 价值更新 数量更新 PP-PI 流程制造 生产计划 容差配置 SAP事务码 SAP基础 TCODE Basis 事务代码 MMNR 编号区间 采购实操 组织架构 OMSF SAP实操 FI配置
联系我们
联系电话:15090125178(微信同号)
电子邮箱:garson_zhang@163.com
站长微信二维码
微信二维码