34、使用 Rx.NET 进行事件处理与异步编程

使用 Rx.NET 进行事件处理与异步编程

1. 使用 Rx.NET 异步搜索 Flickr 照片

在这部分,我们将构建一个使用 Rx.NET 异步搜索 Flickr 照片的应用程序。通过这个示例,你将学习如何从事件创建可观察数据源,以及如何对这些数据源进行订阅。

1.1 创建 Windows Phone 项目

创建一个新的 Windows Phone 项目用于 Flickr 图片搜索,具体步骤如下:
1. 启动 Visual Studio 2010 Express for Windows Phone,创建一个新的 Windows Phone 应用程序项目,将其命名为 FlickrRx
2. 将应用程序名称更改为 Flickr Search ,页面标题更改为 Rx at Work 。具体操作是:选中应用程序名称,按下 F4,编辑 Text 属性,然后对页面标题进行相同操作。
3. 在解决方案资源管理器中右键单击项目名称,选择“添加引用”,添加 Microsoft.Phone.Reactive System.Observable 程序集。

1.2 添加用户界面

为项目添加 UI 元素,UI 由一个文本框、一个标签和一个 WebBrowser 控件组成,步骤如下:
1. 从工具箱中选择一个文本框,拖放到设计界面上,将其重命名为 txtSearchTerms 。使文本框的宽度等于屏幕宽度,并清空 Text 属性。接着选择一个文本块,将其放置在文本框下方,重命名为 lblSearchingFor ,并将其宽度调整为屏幕宽度。
2. 从工具箱中选择 WebBrowser 控件,将其拖放到文本块下方的设计界面上,重命名为 webResults ,并使其宽度等于屏幕宽度。

1.3 添加搜索 Flickr 图片的逻辑

添加逻辑以使用 Flickr 图片搜索结果填充 WebBrowser 控件,步骤如下:
1. 打开 MainPage.xaml.cs (右键单击 MainPage.xaml 并选择“查看代码”),在页面顶部粘贴以下 using 语句:

using Microsoft.Phone.Reactive;
  1. MainPage() 构造函数的 InitializeComponent() 语句之后立即粘贴以下代码:
var keys =  Observable.FromEvent<KeyEventArgs>(txtSearchTerms, "KeyUp");

keys.Subscribe(evt =>
{
    lblSearchingFor.Text = "Searching for ..." + txtSearchTerms.Text;
    webResults.Navigate(new Uri("http://www.flickr.com/search/?q=" + txtSearchTerms.Text));
});

上述代码中,第一行代码创建了一个可观察数据源 keys ,它包含了 txtSearchTerms 文本框的所有 KeyUp 事件。第二行代码是一个 lambda 表达式,它在这个集合上创建了一个观察者,并尝试使用文本框中输入的文本更新 lblSearchingFor 文本块。同时,它会显示代表使用文本框中提供的文本在 Flickr 上搜索结果的网页。

  1. 按下 F5 运行应用程序。当你输入第一个字符时,应该会看到 WebBrowser 控件尝试导航到 Flickr 搜索页面,将输入的唯一字符作为搜索条件。注意,几乎没有视觉指示表明后台正在进行搜索或导航操作。在后续部分,我们将创建一个动画,在 WebBrowser 控件加载图片搜索结果时播放。
2. 使用节流功能增强 Flickr 搜索

到目前为止,你可能会疑惑,除了观察者模式的复杂性,Rx.NET 为你的工具集增添了什么。其实到目前为止,Rx.NET 并没有带来太多不同,你使用标准的事件处理程序也能实现相同的功能。不过,在接下来的步骤中,Rx.NET 的强大之处将逐渐显现。

2.1 修改应用程序代码

对应用程序进行以下修改:
1. 将声明可观察集合的代码行从:

var keys =  Observable.FromEvent<KeyEventArgs>(txtSearchTerms, "KeyUp");

修改为:

var keys =  Observable.FromEvent<KeyEventArgs>(txtSearchTerms, "KeyUp").Throttle(TimeSpan.FromSeconds(.5));
  1. 将声明观察者的代码块从:
keys.Subscribe(evt =>
{
    lblSearchingFor.Text = "Searching for ..." + txtSearchTerms.Text;
    webResults.Navigate(new Uri("http://www.flickr.com/search/?q=" + txtSearchTerms.Text));
});

修改为:

keys.ObserveOn(Deployment.Current.Dispatcher).Subscribe(evt =>
{
    if (txtSearchTerms.Text.Length > 0)
    {
        lblSearchingFor.Text = "Searching for ..." + txtSearchTerms.Text;
        webResults.Navigate(new Uri("http://www.flickr.com/search/?q=" + txtSearchTerms.Text));
    }
});
  1. 按下 F5 运行应用程序。点击文本框,输入在 Flickr 上查找照片的搜索词(例如,输入 Barcelona ),观察 WebBrowser 控件从 Flickr 检索那个美丽欧洲城市的图片。
2.2 代码分析

我们创建了一个可观察集合,它包含了 txtSearchTerms 文本框上生成的所有 KeyUp 事件。添加 Throttle(.5) 语句后,实际上是告诉 Rx.NET 只观察间隔超过半秒(0.5 秒)的 KeyUp 事件。假设普通用户按键间隔小于半秒,按键之间的半秒暂停告诉应用程序用户已准备好启动 Flickr 搜索,并准备“观察”其执行结果。

另外,在修改后的代码中,添加了逻辑,若文本框中未输入任何内容(例如用户使用退格键删除了内容),则不调用图片搜索。同时,使用了 ObserveOn(Deployment.Current.Dispatcher) 构造来帮助创建观察者。如果移除这个构造,代码将变为:

keys.Subscribe(evt =>
{
    if (txtSearchTerms.Text.Length > 0)
    {
        lblSearchingFor.Text = "Searching for ..." + txtSearchTerms.Text;
        webResults.Navigate(new Uri("http://www.flickr.com/search/?q=" + txtSearchTerms.Text));
    }
});

再次按下 F5 运行应用程序,会看到 Visual Studio 显示“Invalid cross-thread access”消息。这是因为在 .NET 平台上,从非 UI 线程更新 UI 是一个棘手的问题。Rx.NET 在后台创建了一个单独的线程,并从该线程将可观察数据源的更改通知推送给观察者,而这个后台线程不能直接修改 UI 线程。幸运的是,Rx.NET 的开发者提供了 ObserveOn() 扩展方法来解决这个问题,该方法可以接受一个 Dispatcher 对象,从而让你在 UI 线程上观察可观察数据源。

3. 添加 Flickr 图片加载时播放的动画

可以通过添加一个简单的动画来进一步增强 Flickr 图片搜索应用程序,该动画将在图片搜索结果网页加载时播放。具体步骤如下:

3.1 创建动画
  1. 仍然在 Windows Phone 的 Visual Studio 中,向手机设计界面添加一个文本块,将其放置在“Searching for”文本块和 WebBrowser 控件之间。将该文本块命名为 lblLoading ,将标题设置为 Loading Images ,并将其 Visibility 属性设置为 Collapsed
  2. 要启动 Expression Blend 并加载 Flickr 项目,在 Visual Studio 中右键单击 MainPage.xaml 并选择“在 Expression Blend 中打开”。Microsoft Expression Blend 将启动并打开你的解决方案,准备进行编辑。
  3. 在 Expression Blend 中,从工具箱中选择一个矩形,在 Loading Images 文本块旁边绘制一个非常窄(几乎不可见)的矩形,并将其填充颜色设置为红色。
  4. 在“对象和时间线”窗口中,点击“新建”按钮,将故事板命名为 loadingImages ,然后点击“确定”。
  5. 选择你放置在 Windows Phone 设计界面上的矩形,点击“记录关键帧”按钮。
  6. 将动画播放头(时间线中的黄色垂直线)移动到大约 1.5 秒处,再次点击“记录关键帧”按钮,然后将矩形的大小调整为接近手机屏幕的全宽。
  7. 在“对象和时间线”中,点击并选择 loadingImages 故事板名称。出现故事板的“通用属性”对话框,在该故事板的“重复行为”属性中选择“永远”。
  8. 在 Expression Blend 中保存所有内容(文件 -> 全部保存),然后切换回 Visual Studio。
3.2 添加代码控制动画
  1. 为了在用户发起 Flickr 图片搜索时启动动画,调用 loadingImages 动画的 Begin 方法。在 MainPage.xaml.cs 中,将创建 KeyUp 事件观察者的代码更改为:
keys.ObserveOn(Deployment.Current.Dispatcher).Subscribe(evt =>
{
    if (txtSearchTerms.Text.Length > 0)
    {
        lblSearchingFor.Text = "Searching for ..." + txtSearchTerms.Text;
        lblLoading.Visibility = System.Windows.Visibility.Visible;
        loadingImages.Begin();
        webResults.Navigate(new Uri("http://www.flickr.com/search/?q=" + txtSearchTerms.Text));
    }
});
  1. 一旦图片在 WebBrowser 控件中加载完成,通过调用 loadingImages 动画的 Stop 方法停止动画。为此,使用 Rx.NET 订阅 WebBrowser 的 Navigated 事件。当此订阅接收到数据时,停止动画。在 MainPage 构造函数的末尾粘贴以下代码:
var browser =  Observable.FromEvent<System.Windows.Navigation.NavigationEventArgs>(webResults, "Navigated");

browser.ObserveOn(Deployment.Current.Dispatcher).Subscribe(evt =>
{
    loadingImages.Stop();
    lblLoading.Visibility = System.Windows.Visibility.Collapsed;
});
  1. 按下 F5,在文本框中输入一个关键字,观察图片加载到浏览器时的动画。

通过以上步骤,我们不仅实现了一个基本的 Flickr 图片搜索应用程序,还通过节流功能优化了搜索体验,并添加了动画增强了用户交互性。Rx.NET 的强大之处在于它可以将任何事件视为可观察数据源,无论是位置服务生成的坐标值、加速度计数据、按键事件还是 Web 浏览器事件。接下来,我们将继续学习 Rx.NET 的设计准则,并使用它构建一个简单的实时天气应用程序。

4. Rx.NET 设计准则

Rx.NET 已经有了自己的一套设计准则(可在 http://go.microsoft.com/fwlink/?LinkID=205219 查看),以帮助开发者在使用该库时做出最佳决策。下面介绍其中两个有用的策略。

4.1 考虑绘制弹珠图

如果你在 Microsoft 网站(特别是 Channel 9)上研究 Rx.NET,很可能会遇到所谓的弹珠图。弹珠图有助于理解输入的基于事件和异步的数据序列在应用给定的 Rx.NET 运算符后会发生什么。

例如,在一个接收手机地理位置的实际示例中,假设在用户输入新的感兴趣位置进行搜索之前,一直在收集手机用户的位置数据。最初,顶部的数据序列( geoLocationReading )接收数据点(每个数据点用一个小圆圈或弹珠表示)。然后, newLocation 序列开始接收数据。应用 TakeUntil() 运算符后,结果序列( result )只会获取 geoLocationReading 序列中直到 newLocation 序列开始有数据点之前的数据点。之后,无论 geoLocationReading newLocation 序列上出现多少数据点,结果序列都不会再获取额外的数据点。

通过在弹珠图上可视化 Rx.NET 运算符并理解它们产生的结果序列,你可以分析所有的 Rx.NET 运算符。此外,绘制你想要创建的序列的弹珠图,然后就可以确定需要使用哪些 Rx.NET 运算符来实现该序列。

4.2 考虑向引入并发的运算符传递特定的调度器

Rx.NET 可以通过抽象线程和并发的许多问题,并以声明式方式处理并发,从而使并发编程更简单。不过,即使有这种并发抽象,你仍然可以控制 Rx.NET 的执行方式以及 Rx.NET 通知的处理上下文,这就涉及到调度器。

在 Rx.NET 中,可以调度两件事:订阅的执行线程上下文和通知的发布线程上下文。这可以通过 IObservable<T> 接口的 SubscribeOn() ObserveOn() 扩展方法来控制。这两个扩展方法都可以接受 Scheduler 静态类的属性,以下是可用的属性及其说明:
| 调度器属性 | 说明 |
| — | — |
| Scheduler.Dispatcher | 强制在拥有应用程序线程并内部维护工作项队列的 Dispatcher 上执行。 |
| Scheduler.NewThread | 将所有操作调度到一个新线程上。 |
| Scheduler.ThreadPool | 将所有操作调度到线程池上。 |
| Scheduler.Immediate | 确保操作立即执行。 |
| Scheduler.CurrentThread | 确保操作在进行原始调用的线程上执行。这与 Scheduler.Immediate 不同,因为在当前线程上调度的操作可能会排队等待稍后执行。 |

以下是在 Rx.NET 中使用调度器进行订阅的示例代码:

Observable.FromAsyncPattern<WebResponse>(
    webRequest.BeginGetResponse,
    webRequest.EndGetResponse)()
    .SubscribeOn(Scheduler.NewThread)
    .ObserveOn(Scheduler.Dispatcher)
    .Subscribe(

Rx 设计准则认为,在适当的地方传递调度器是最佳实践,这样可以从一开始就在正确的位置创建并发。

5. 使用 Rx.NET 与 Web 服务异步检索天气数据

在这部分,我们将使用位于 www.webservicex.net/WS/WSDetails.aspx?CATID=12&WSID=56 的公共天气 Web 服务来检索并显示美国给定城市的当前天气。除了天气服务,该位置还提供许多其他有用的 Web 服务,包括邮政编码验证和货币转换。

从开发的角度来看,天气应用程序将包括异步捕获用户输入(城市名称)、异步调用 Web 服务,然后显示该城市的当前天气。接下来,我们将着手创建这个应用程序。

通过前面的学习,我们已经掌握了使用 Rx.NET 进行事件处理、优化搜索体验、添加动画以及遵循设计准则等知识。在构建天气应用程序的过程中,我们将进一步巩固这些知识,并学习如何处理错误恢复等概念。

下面是创建 Flickr 图片搜索应用程序的流程图:

graph LR
    A[创建 Windows Phone 项目] --> B[添加用户界面]
    B --> C[添加搜索逻辑]
    C --> D[使用节流增强搜索]
    D --> E[添加加载动画]

通过这个流程图,我们可以清晰地看到创建 Flickr 图片搜索应用程序的主要步骤。从项目创建开始,逐步添加界面、逻辑、优化功能和动画效果,最终完成一个功能丰富的应用程序。在后续构建天气应用程序时,我们也可以参考类似的流程,逐步实现各个功能模块。

使用 Rx.NET 进行事件处理与异步编程

6. 构建天气应用程序的准备工作

在构建天气应用程序之前,我们需要了解一些基本信息。该应用程序将使用位于 www.webservicex.net/WS/WSDetails.aspx?CATID=12&WSID=56 的公共天气 Web 服务,异步获取并显示美国给定城市的当前天气。

以下是构建该应用程序的大致步骤:
1. 创建项目:在 Visual Studio 中创建一个新的 Windows Phone 应用程序项目。
2. 添加引用:添加必要的引用,确保可以使用 Rx.NET 相关功能。
3. 设计界面:设计一个简单的界面,包含输入城市名称的文本框和显示天气信息的区域。
4. 实现逻辑:使用 Rx.NET 异步捕获用户输入的城市名称,调用 Web 服务获取天气数据,并将结果显示在界面上。

7. 具体实现步骤
7.1 创建项目
  1. 打开 Visual Studio 2010 Express for Windows Phone。
  2. 创建一个新的 Windows Phone 应用程序项目,命名为 WeatherApp
7.2 添加引用

在解决方案资源管理器中,右键单击项目名称,选择“添加引用”,添加 Microsoft.Phone.Reactive System.Observable 程序集,确保可以使用 Rx.NET 的功能。

7.3 设计界面

MainPage.xaml 中设计界面,示例代码如下:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <TextBox x:Name="txtCityName" HorizontalAlignment="Left" Height="72" Margin="10,10,0,0" TextWrapping="Wrap" Text="Enter City Name" VerticalAlignment="Top" Width="460"/>
    <TextBlock x:Name="lblWeatherInfo" HorizontalAlignment="Left" Margin="10,100,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="460"/>
</Grid>

上述代码创建了一个文本框 txtCityName 用于输入城市名称,以及一个文本块 lblWeatherInfo 用于显示天气信息。

7.4 实现逻辑

打开 MainPage.xaml.cs ,在页面顶部添加 using 语句:

using Microsoft.Phone.Reactive;
using System.Net;

MainPage() 构造函数中添加以下代码:

var cityInput = Observable.FromEvent<KeyEventArgs>(txtCityName, "KeyUp");

cityInput.Throttle(TimeSpan.FromSeconds(1))
         .ObserveOn(Deployment.Current.Dispatcher)
         .Subscribe(evt =>
         {
             if (txtCityName.Text.Length > 0)
             {
                 string city = txtCityName.Text;
                 string url = $"http://www.webservicex.net/globalweather.asmx/GetWeather?CityName={city}&CountryName=USA";

                 WebClient client = new WebClient();
                 client.DownloadStringCompleted += (sender, e) =>
                 {
                     if (e.Error == null)
                     {
                         string weatherData = e.Result;
                         // 这里可以对 weatherData 进行解析,提取有用的天气信息
                         lblWeatherInfo.Text = weatherData;
                     }
                     else
                     {
                         lblWeatherInfo.Text = "Error retrieving weather data.";
                     }
                 };
                 client.DownloadStringAsync(new Uri(url));
             }
         });

上述代码实现了以下功能:
1. 创建一个可观察数据源 cityInput ,包含 txtCityName 文本框的所有 KeyUp 事件。
2. 使用 Throttle 方法确保只有在用户输入间隔超过 1 秒时才会触发后续操作。
3. 使用 ObserveOn 方法确保操作在 UI 线程上执行,避免跨线程访问问题。
4. 当用户输入城市名称后,构造一个包含城市名称的 URL,使用 WebClient 异步下载天气数据。
5. 当下载完成后,根据结果更新 lblWeatherInfo 文本块的内容。

8. 错误处理与恢复

在实际应用中,可能会遇到各种错误,如网络连接问题、服务器响应错误等。为了提高应用程序的健壮性,我们需要进行错误处理和恢复。

在上述代码中,我们已经对 WebClient DownloadStringCompleted 事件进行了简单的错误处理。当下载过程中出现错误时,会在界面上显示错误信息。

以下是一个更完善的错误处理示例:

cityInput.Throttle(TimeSpan.FromSeconds(1))
         .ObserveOn(Deployment.Current.Dispatcher)
         .Subscribe(evt =>
         {
             if (txtCityName.Text.Length > 0)
             {
                 string city = txtCityName.Text;
                 string url = $"http://www.webservicex.net/globalweather.asmx/GetWeather?CityName={city}&CountryName=USA";

                 WebClient client = new WebClient();
                 client.DownloadStringCompleted += (sender, e) =>
                 {
                     if (e.Error == null)
                     {
                         string weatherData = e.Result;
                         try
                         {
                             // 解析天气数据
                             // 这里可以添加更复杂的解析逻辑
                             lblWeatherInfo.Text = weatherData;
                         }
                         catch (Exception ex)
                         {
                             lblWeatherInfo.Text = "Error parsing weather data: " + ex.Message;
                         }
                     }
                     else
                     {
                         if (e.Error is WebException webEx)
                         {
                             switch (webEx.Status)
                             {
                                 case WebExceptionStatus.NameResolutionFailure:
                                     lblWeatherInfo.Text = "Network error: Name resolution failed.";
                                     break;
                                 case WebExceptionStatus.ConnectFailure:
                                     lblWeatherInfo.Text = "Network error: Unable to connect to the server.";
                                     break;
                                 case WebExceptionStatus.ProtocolError:
                                     lblWeatherInfo.Text = "Server error: " + ((HttpWebResponse)webEx.Response).StatusCode;
                                     break;
                                 default:
                                     lblWeatherInfo.Text = "Network error: " + webEx.Status;
                                     break;
                             }
                         }
                         else
                         {
                             lblWeatherInfo.Text = "Error retrieving weather data: " + e.Error.Message;
                         }
                     }
                 };
                 client.DownloadStringAsync(new Uri(url));
             }
         });

在这个示例中,我们不仅处理了下载过程中的错误,还对解析天气数据时可能出现的异常进行了捕获和处理。同时,针对不同类型的网络错误,给出了更详细的错误信息。

9. 总结与展望

通过前面的学习和实践,我们已经掌握了使用 Rx.NET 进行事件处理、异步编程、优化搜索体验、添加动画、遵循设计准则以及处理错误恢复等知识。从构建 Flickr 图片搜索应用程序到天气应用程序,我们逐步深入了解了 Rx.NET 的强大功能。

以下是使用 Rx.NET 开发应用程序的一些优点总结:
| 优点 | 说明 |
| — | — |
| 简化异步编程 | Rx.NET 抽象了线程和并发的许多问题,使异步编程变得更加简单和直观。 |
| 统一事件处理 | 可以将任何事件视为可观察数据源,使用统一的方式处理各种事件。 |
| 提高代码可读性和可维护性 | 通过声明式的编程方式,使代码更加简洁和易于理解。 |
| 支持错误处理和恢复 | 可以方便地处理各种异常情况,提高应用程序的健壮性。 |

展望未来,Rx.NET 还有很多其他的应用场景和功能等待我们去探索。例如,可以结合其他技术,如传感器数据处理、实时数据分析等,开发出更加复杂和强大的应用程序。同时,随着技术的不断发展,Rx.NET 也会不断更新和完善,为开发者提供更多的便利和可能性。

下面是构建天气应用程序的流程图:

graph LR
    A[创建项目] --> B[添加引用]
    B --> C[设计界面]
    C --> D[实现逻辑]
    D --> E[错误处理与恢复]

通过这个流程图,我们可以清晰地看到构建天气应用程序的主要步骤。从项目创建开始,逐步添加引用、设计界面、实现逻辑,并进行错误处理和恢复,最终完成一个功能完善、健壮的天气应用程序。在未来的开发中,我们可以根据这个流程,结合具体需求,开发出更多优秀的应用程序。

本设计项目聚焦于一款面向城市环保领域的移动应用开发,该应用以微信小程序为载体,结合SpringBoot后端框架MySQL数据库系统构建。项目成果涵盖完整源代码、数据库结构文档、开题报告、毕业论文及功能演示视频。在信息化进程加速的背景下,传统数据管理模式逐步向数字化、系统化方向演进。本应用旨在通过技术手段提升垃圾分类管理工作的效率,实现对海量环保数据的快速处理整合,从而优化管理流程,增强事务执行效能。 技术上,前端界面采用VUE框架配合layui样式库进行构建,小程序端基于uni-app框架实现跨平台兼容;后端服务选用Java语言下的SpringBoot框架搭建,数据存储则依托关系型数据库MySQL。系统为管理员提供了包括用户管理、内容分类(如环保视频、知识、新闻、垃圾信息等)、论坛维护、试题测试管理、轮播图配置等在内的综合管理功能。普通用户可通过微信小程序完成注册登录,浏览各类环保资讯、查询垃圾归类信息,并参在线知识问答活动。 在设计实现层面,该应用注重界面简洁性操作逻辑的一致性,在满足基础功能需求的同时,也考虑了数据安全性系统稳定性的解决方案。通过模块化设计规范化数据处理,系统不仅提升了管理工作的整体效率,也推动了信息管理的结构化自动化水平。整体而言,本项目体现了现代软件开发技术在环保领域的实际应用,为垃圾分类的推广管理提供了可行的技术支撑。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
内容概要:本文系统介绍了敏捷开发在汽车电子架构设计中的应用背景、核心理念及其相较于传统瀑布式开发的优势。文章从传统开发流程存在的问题切入,阐述了敏捷开发兴起的原因,并深入解析《敏捷软件开发宣言》提出的四大价值观:个体和互动高于流程和工具、工作的软件高于详尽的文档、客户合作高于合同谈判、响应变化高于遵循计划。重点强调敏捷开发以迭代为核心实践方式,通过小步快跑、持续交付可运行软件、频繁获取反馈来应对需求变化,提升开发效率客户价值。同时展示了敏捷开发在互联网和汽车行业的广泛应用,如苹果、谷歌、博世、奔驰等企业的成功实践,证明其在智能化转型中的普适性和有效性。; 适合人群:从事汽车电子、嵌入式系统开发的工程师,以及对敏捷开发感兴趣的项目经理、产品经理和技术管理者;具备一定软件开发背景,希望提升开发效率和团队协作能力的专业人士。; 使用场景及目标:①理解敏捷开发相对于传统瀑布模型的核心优势;②掌握敏捷开发四大价值观的内涵及其在实际项目中的体现;③借鉴行业领先企业的敏捷转型经验,推动团队或组织的敏捷实践;④应用于智能汽车、车联网等快速迭代系统的开发流程优化。; 阅读建议:此资源侧重理念实践结合,建议读者结合自身开发流程进行对照反思,在团队中推动敏捷思想的落地,注重沟通协作机制建设,并从小范围试点开始逐步实施迭代开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值