>示例章节

实现数据访问

程序在运行时可以使用变量来存储值,但它需要一种持久化数据的方法。在这本书中,我们已经谈到了数据存储,本节中的技能3.1选择适当的数据收集类型,“我们考虑了如何最好地将应用程序中的信息映射到C#提供的数据类型上。本节后面的部分使用JSON,“我们看到了如何序列化对象中的数据。最后,在“使用实体框架设计数据存储我们创建的类被自动映射到数据库表中,以便在应用程序中使用。在阅读本章之前,这些话题值得一遍,这样才能让你头脑清醒。

在本章中,我们将把所有这些不同的数据存储和访问能力结合在一起,并考虑C#程序如何存储和操作数据;从文件存储的使用和管理开始,然后进入数据库,然后介绍语言集成查询(LINQ)的使用。然后我们将详细研究序列化,最后探讨.NET框架提供的数据收集工具。

技能4.1:执行I/O操作

在本节中,我们将研究应用程序中支持数据存储的基本输入/输出(I/O)操作。您将发现操作系统和.NET Framework库如何管理文件,这些库允许程序存储和加载数据。与现代处理器的速度相比,文件访问是非常慢的活动,所以我们还要研究异步i/o的使用,即使应用程序正在读取或写入大量数据,也可以使用它来保持响应性。

读写文件和流

流是表示数据流的软件对象。NET框架提供了河流类,该类充当用于读取和写入数据的一系列类的父类型。程序可以以三种方式与流交互:

  • 将字节序列写入流

  • 从流中读取字节序列

  • 定位文件指针在溪流中

文件指针是流中下一个读或写操作将要发生的位置。程序可以使用寻找由流提供的方法来设置此位置。例如,程序可以通过连接到格式化记录的文件流搜索具有特定名称的记录。

这个河流类是抽象的,用作连接到实际存储资源的流的模板。这是一个很好的例子,展示了如何使用C#类来部署资源。可以使用河流可以处理任何行为像流的对象。在清单3-24中的第三章中,我们使用了内存流将加密过程的输出捕获到字节数组中。通过使用不同类型的流对象,很容易将程序中产生的加密数据重定向到文件或网络连接。图4-2显示了System.IO.Stream类型是提供不同形式的流连接的大量类的基类型。

图4-1

图4-1一些流类型

子类都包含允许传输数据的流行为,例如河流方法可以在它们中的任何一个上使用来向该流写入字节。然而,如何创建每种类型的流取决于该流类型。例如,创建文件流程序必须指定文件的路径以及如何使用该文件。创建内存流程序必须指定要使用的内存中的缓冲区。

使用文件流

这个文件流对象提供连接到文件的流实例。流对象实例将流中的调用转换为运行程序的计算机上的文件系统的命令。文件系统向执行计算机的数据存储的物理设备提供接口。图4-2显示这是如何工作的。一个电话流对象中的方法将生成对文件系统的请求,以将数据写入存储设备。

图4-2

图4-2流对象

清单4-1显示了程序如何使用文件流创建连接到新文件或现有文件的输出流。程序向该流写入一个字节块。然后创建一个新的流,用于从文件中读取字节。要写入的字节是通过对文本字符串进行编码而获得的。

使用文件流列出4-1

使用系统;使用System.IO;使用系统。文本;名称空间LISTING_4_1_use_a_FileStream{class Program{static void.(string[]args){{FileStream outputStream=new FileStream("OutputText.txt”,文件格式。OpenOrCreate,FileAccess.Write;字符串outputMessageString="你好,世界;byte[]outputMessageBytes=Encoding.UTF8.GetBytes(outputMessageString);outputStream.write(outputMessageBytes,0,输出MessageBytes.Length;outputStream.Close();FileStream inputStream=new FileStream("OutputText.txt”,文件模式。打开,FileAccess.Read)long fileLength=inputStream..;byte[]readBytes=new byte[fileLength];inputStream.Read(readBytes,0,(int)文件长度;字符串readString=Encoding.UTF8.GetString(readBytes);inputStream.Close();Console.WriteLine("读取消息:{0},readString;控制台,ReadKey();} }

控制文件与FileMode和FileAccess一起使用

流可以与读取相关联,写作,或者更新文件。底座河流类提供程序可用于确定给定流实例的能力(程序是否可以读取,写,或者在这条小溪上寻找)。

这个文件格式枚举用于文件流以指示如何打开文件。以下模式可用:

  • 文件模式。追加打开附加到末尾的文件。如果文件存在,将查找位置移动到这个文件的末尾。如果文件不存在;创造它。只有打开文件进行写入时,才能使用此模式。

  • 文件模式。创建创建用于写入的文件。如果文件已经存在,它被覆盖了。注意,这意味着文件的现有内容丢失。

  • File..CreateNew创建用于写入的文件。如果文件已经存在,引发异常。

  • 文件模式。打开打开现有文件。如果文件不存在,则引发异常。这种模式可用于阅读或写作。

  • File..OpenOrCreate打开一个文件用于读或写。如果文件不存在,创建一个空文件。这种模式可用于阅读或写作。

  • 文件模式。截断打开用于写入的文件并删除任何现有内容。

这个文件访问枚举用于指示如何使用文件。以下访问类型可用:

  • 文件访问,读取打开文件进行读取。

  • 文件访问.读写打开一个文件用于读或写。

  • 文件访问。写入打开文件进行写入。

您可以在清单4-1中看到这些用法。如果文件流的使用方式与打开方式不兼容,该操作将异常失败。

用Unicode将文本转换为二进制数据

流只能向存储设备传送字节数组或从存储设备传送字节数组,清单4-1中的程序使用编码来自系统.文本命名空间。这个UTF8这个类的属性提供了对Unicode文本进行编码和解码的方法。我们在本节中查看了Unicode”字符串比较和文化,“技能2.7。

Unicode是字符符号到数值的映射。这个UTF8编码将Unicode字符映射到可以存储在字节数组中的8位值上。大多数文本文件都使用UTF8进行编码。编码类还提供了对其他编码标准的支持,包括UTF32(对32位值的Unicode编码)和ASCI。

这个获取字节编码方法采用C#字符串,并返回在指定编码中表示该字符串的字节。这个获取字符串解码方法接受字节数组,并返回一个由字节组成的缓冲区表示的字符串。

IDispose和FileStream对象

这个河流类实现可识别的技能2.4所示的接口。这意味着任何从河流类型还必须实现接口。这意味着我们可以使用C#使用构造以确保当不再需要文件时关闭它们。清单4-2显示了它的工作原理。

列出4-2文件流和标识

使用(FileStream outputStream=new FileStream("OutputText.txt”,文件模式。OpenOrCreate,FileAccess.Write){string outputMessageString="你好,世界;byte[]outputMessageBytes=Encoding.UTF8.GetBytes(outputMessageString);outputStream.write(outputMessageBytes,0,输出MessageBytes.Length;}

处理文本文件

文件系统没有特别区分文本文件和二元的文件夹。我们已经看到了如何使用编码类将Unicode文本转换为可以写入二进制文件的字节数组。然而,C#语言提供了流类,使得处理文本更加容易。这个文本编辑器特克斯特雷德类是定义可与文本一起使用的一组方法的抽象类。

这个流水写手类扩展文本编辑器类来提供一个类,我们可以将文本写入流。清单4-3显示了流水写手流式阅读器类可用于处理文本文件。它执行与清单4-1中的程序相同的任务,但是它更紧凑。

列出4-3StreamWriter和StreamReader

使用(StreamWriter writeStream=new StreamWriter("OutputText.txt”{writeStream.write("你好,世界;}使用(StreamReader readStream=new StreamReader("OutputText.txt”{string readSTring=readStream.ReadToEnd();Console.WriteLine("文本读取:{0},阅读;}

把小溪串在一起

这个河流类的构造函数接受另一个流作为参数,允许创建流链。清单4-4显示了如何使用GZIPFISHSystem.IO.压缩保存和加载压缩文本的流链中的名称空间。

列出4-4存储压缩文件

使用(FileStream writeFile=new FileStream("CompText.zip”,文件模式。OpenOrCreate,使用(GZipStream writeFileZip=new GZipStream(writeFile,Compression..Comp.){use(StreamWriter writeFileText=new StreamWriter(writeFileZip)){writeFileText.Write("你好,世界;}}使用(FileStream readFile=new FileStream("CompText.zip”,文件模式。打开,使用(GZipStream readFileZip=new GZipStream(readFile,使用(StreamReader readFileText=new StreamReader(readFileZip)){{string message=readFileText.ReadToEnd();Console.WriteLine("读取文本:{0},消息);} }

使用File类

这个文件类是帮手类,使处理文件更加容易。它包含一组静态方法,可用于将文本附加到文件中,复制文件,创建文件,删除文件,移动文件,打开文件,读取文件,管理文件安全。清单4-5显示了文件集体行动。

列出4-5文件类

File.WriteAllText(路径:文本文件.txt”,内容:这篇课文归档了;File.AppendAllText(路径:文本文件.txt”,内容:-到最后;如果(File.Ex.("文本文件.txt”Console.WriteLine("文本文件存在”;字符串内容=File.ReadAllText(路径:文本文件.txt”;Console.WriteLine("文件内容:{0}”,内容);File.Copy(sourceFileName):文本文件.txt”,destFileName:CopyTextFile.txt”;使用(TextReader reader=File.OpenText(路径):CopyTextFile.txt”{string text=reader.ReadToEnd();Console.WriteLine("复制文本:{0},文本);}

处理流异常

对于给定的执行线程来说,继续执行是没有意义的。线程可以引发异常,并将控制传递给处理程序,处理程序将尝试以合理的方式解决该情况。您首先看到了技能1.5中的异常,“执行异常处理。”在创建使用流的应用程序时,需要确保代码能够处理流可能引发的任何异常。这些可以在流使用期间的任何时间发生。我们的应用程序可能试图打开不存在的文件,或者给定存储设备在写入期间可能变得满。多线程应用程序中的线程也可以“战斗”越过文件。如果一个线程试图访问另一个线程已经使用的文件,这将导致引发异常。

考虑到这一点,您应该确保打开流并与流交互的生产代码受到以下内容的保护试捕建筑。有一组文件异常用于指示不同的错误条件。清单4-6显示了程序如何检测FileNotFoundException并以不同的方式响应其他文件异常。

列出4-6流异常

尝试{string.=File.ReadAllText(path):txt”;控制台,写线(内容);}.(FileNotFoundException notFoundEx){//File not.Console.WriteLine(notFoundEx.Message);}.(Exception ex){//任何其他异常Console.WriteLine(ex.Message);}

文件存储

给定存储设备,可能是磁盘驱动器或USB便携式磁盘,可分为隔板.每个分区表示存储设备上可用于存储数据的区域。存储设备上的分区公开为开车哪一个,在Windows操作系统上,由驱动字母。驱动器字母由操作系统分配,用作绝对路径到计算机上的一个文件。

磁盘管理应用程序允许管理员重新分配驱动器字母,将多个物理驱动器组合为单个逻辑驱动器,并附加从驱动器映像创建的虚拟硬盘(VHD)。图4-3下面显示了这个正在使用的程序。

图4-3

图4-3磁盘管理程序

物理存储设备上的每个分区是格式化的使用特定的归档系统管理文件存储的。清单4-7中的程序显示了驾驶信息IO系统名称空间可用于获得关于附加到系统的驱动器的信息。

列出4-7驱动器信息

DriveInfo[]drives=DriveInfo.GetDrives();foreach(驱动器中的驱动器信息驱动器){Console.Write("姓名:{ 0 },驱动器.名称;如果(驱动器.IsReady){Console.Write("类型:{ 0 },驱动器类型;控制台.写("格式:{0}”,驱动器.驱动器格式;控制台.写("自由空间:{0}”,驱动器.TotalFreeSpace;}否则{控制台.写入("开车没准备好;}Console.WriteLine();}

当我运行程序时,创建了以下输出:

名称:C:\\\\类型:固定格式:NTFS:固定格式:NTFS自由空间:6970923003008080名称编号:D:\\\\\类型:69696969696969707092300808080808080名称自由空间:697070707070707070707070709230080808080名称编号:固定格式:类型:固定格式:NTFS:固定格式:NTFS自由空间:固定格式:固定格式:NTFS自由空间:13333333860229292929292929292929292929292929292929292929292929292929292929292929292929292929292929290496Name:K:Drive not readyName:L:Drive not.

注意,一些驱动器字母已经分配给可移动设备。驱动器F是来自照相机的存储卡。显示为未准备好的驱动器目前没有物理连接设备。

使用文件信息

文件系统维护关于其存储的每个文件的信息。这包括文件的名称,与该文件相关联的权限,与创建关联的日期,修改文件,以及文件在存储设备上的物理位置。文件系统还维护关于每个文件的属性信息。属性信息被保存为单个值,该值中的不同位指示不同的属性。我们可以使用逻辑运算符来处理这些值并为文件分配不同的属性。可用属性如下:

  • 文件属性.归档文件还没有备份。当备份文件时/如果备份文件,属性将被清除。

  • 文件属性。压缩文件被压缩。这不是我们的程序应该改变的。

  • FileAttributes.目录该文件是一个目录。这不是我们的程序应该改变的。

  • 隐藏的文件属性该文件不会出现在普通目录列表中。

  • 文件属性。正常这是一个没有特殊属性的普通文件。这个属性只有在没有分配给文件的其他属性时才有效。

  • FileAttributes.Read.无法写入文件。

  • FileAttributes.System该文件是操作系统的一部分,由它使用。

  • FileAttributes.临时的该文件是一个临时文件,当应用程序完成时不需要它。文件系统将尝试将此文件保存在内存中以提高性能。

该信息通过以下方式公开给C#程序文件信息班级。清单4-8显示了程序如何获得文件信息关于文件的信息,然后处理属性信息。程序创建一个新文件,然后获得文件信息对象,该对象表示文件。它使用属性财产文件信息对象,以生成文件只读然后删除只读属性。

使用FileInfo列出4-8

字符串filePath="文本文件.txt”;File.WriteAllText(路径:filePath,内容:这篇课文归档了;FileInfo info=new FileInfo(filePath);Console.WriteLine("姓名:{ 0 },名称;Console.WriteLine("完整路径:{0},info.FullName;Console.WriteLine("最后访问:{0}”,info.LastAccessTime;Console.WriteLine("长度:{0},长度;Console.WriteLine("属性:{0}”,信息、属性;Console.WriteLine("使文件只读;attributes|=FileAttributes.Read.;Console.WriteLine("属性:{0}”,信息、属性;Console.WriteLine("删除只读属性;attributes&=~FileAttributes.Read.;Console.WriteLine("属性:{0}”,信息、属性;;

您可以使用文件信息实例打开用于读取和写入的文件,移动文件,重命名文件,以及修改文件的安全设置。由文件信息实例复制文件班级。这个文件当您想要对单个文件执行操作时,类是最有用的。这个文件信息类在处理大量文件时非常有用。在下一节中,您将发现如何获取文件信息目录中的项目并检查它们。

使用Directory和DirectoryInfo类

文件系统可以创建包含文件信息项集合的文件。这些称为目录文件夹.目录可以包含关于目录的目录信息,允许用户用于创建树结构的目录。

和档案一样,使用目录有两种方法:号码簿类与DirectoryInfo班级。这个号码簿类就像文件班级。它是一个静态类,提供了枚举目录内容以及创建和操作目录的方法。清单4-9显示了程序如何使用号码簿类以创建目录,证明它的存在,然后删除它。注意,如果程序试图删除不为空的目录,则将引发异常。

列出4-9目录类

Directory.CreateDirectory("“测试”;如果(目录.存在("“测试”Console.WriteLine("成功创建的目录;目录.删除("“测试”;Console.WriteLine("成功删除目录;;

实例的DirectoryInfo类描述一个目录的内容。该类还提供了可用于创建和操作目录的方法。清单4-10使用DirectoryInfo班级。

列出4-10DirectoryInfo类

DirectoryInfo localDir=new DirectoryInfo("“测试”;创建();如果(localDir.Ex.)Console.WriteLine("成功创建的目录;localDir.Delete();Console.WriteLine("成功删除目录;;

文件和路径

路径定义文件在存储设备上的位置。在上面的所有示例程序中,我们只是将路径作为文本字符串给出。在这种情况下,正在创建的文件或目录将位于与运行中的程序相同的目录中,并将具有给定的名称。如果要在计算机的不同位置存储文件,则需要创建更复杂的路径。

路径可以是相对的绝对的。相对路径指定文件相对于当前运行程序的文件夹的位置。到目前为止,我们指定的所有路径都与当前目录有关。在表达路径时,性格““.“(期间)具有特殊的意义。单一时期“.“表示当前目录。双重时期““表示当前目录上方的目录。可以使用相对路径指定父目录中的文件,或者树的另一部分的目录中的文件。接下来,您可以看到用于定位图像目录的路径,其中提供了本文的示例程序。这个@字符串开头的字符逐字逐句的字符串。这意味着字符串中的任何转义字符都将被忽略。这是有用的,因为否则字符串中的反斜杠字符可能被解释为转义字符。

字符串imagePath=@”.....图像;;

程序正在调试目录中运行。道路必须”攀登通过四个父目录查找图像目录。在图4-4显示目录的结构。

图4-4

图4-4导航相对路径

绝对路径包括驱动器字母并标识文件路径中的所有子目录。这里的语句给出了文档的路径TXT文件把文件夹放在机器上。

字符串absPath=@"c:usersrobDocumentstest.txt”“

文件的路径包含两个元素:路径中的目录和目录中的文件名。这个路径类提供了许多非常有用的方法,可用于处理程序中的路径。它提供了从完整路径中删除文件名的方法,更改文件名上的扩展名,以及组合文件名和目录路径。清单4-11显示了以下一些方法路径可以使用。

使用路径列出4-11

字符串fullName=@"c:usersrobDocumentstest.txt”;字符串dirName=Path.GetDirectoryName(fullName);字符串fileName=Path.GetFileName(fullName);字符串fileExtension=Path.GetExtension(fullName);字符串lisName=Path.ChangeExtension(fullName,“LIS“;字符串newTest=Path.Combine(dirName,“newtest.txt”;Console.WriteLine("全名: {0},全名);Console.WriteLine("文件目录:{0}”,DrIDENT);Console.WriteLine("文件名:{0}”,文件名);Console.WriteLine("文件扩展名:{0}”,文件扩展;Console.WriteLine("具有lis扩展名的文件:{0}”,;Console.WriteLine("新测试:{0},新测试);;

当您运行清单4-11中的程序时,它产生以下输出:

名为:c:\\\\user\ser\\\\user\\\\\\\\\\\\\\\\\\\\\\\\\\useuseuseuseuseuseuseuseuseuseuseuseuser\\\\\\\\\\\\\\\\\\\\\\\newtest.txt

这个路径类非常有用,应该总是优先使用手动处理路径字符串。这个路径类还提供可以生成临时文件名的方法。

搜索文件

这个DirectoryInfo类提供名为获取文件可以用来获取文件信息描述目录中文件的项。一个过载获取文件可以接受搜索字符串。在搜索字符串中,字符*可以表示任意数量的字符和字符??可以表示单个字符。

清单4-12中的程序使用以下形式获取文件列出示例程序中的所有C#源文件。注意,这个程序还提供了使用递归的良好示例,其中,FindFiles方法调用自身来处理在给定目录中找到的任何目录。

列出4-12C尖锐程序

静态空查找文件(DirectoryInfo dir,字符串搜索模式){foreach(dir.GetDirectories()中的DirectoryInfo目录){FindFiles(目录,搜索模式;}FileInfo[]matchingFiles=dir.GetFiles(search.);foreach(FileInfo fileInfo in matchingFiles){Console.WriteLine(fileInfo.FullName);}静态空白.(string[]args){DirectoryInfo startDir=new DirectoryInfo(@"...........。”;字符串searchString="*CS;查找文件(startDir,searchString;控制台,ReadKey();}

Directory类提供了名为EnumerateFiles的方法,该方法也可以用于以这种方式枚举文件。

使用System.Net名称空间中的类从网络中读取和写入

NET框架提供了一系列应用程序编程接口,这些接口可以与TCP/IP(传输控制协议/互联网协议)网络交互。C#程序可以创建网络插座可以通过发送未确认消息在网络上通信的对象数据报使用UDP(用户数据报协议)或使用TCP(传输控制协议)创建托管连接。

在本节中,我们将重点介绍System.Net名称空间中的类,这些类允许程序使用HTTP(超文本传输协议)与服务器通信。该协议在TCP/IP连接之上运行。换句话说,TCP/IP提供服务器和客户端系统之间的连接,HTTP定义通过该连接交换的消息的格式。

HTTP客户端,例如,网络浏览器,创建到服务器的TCP连接,并通过发送HTTP GET命令对数据进行请求。然后,服务器将响应一页信息。响应返回到客户端之后,TCP连接关闭。

服务器返回的信息使用HTML(超文本标记语言)格式化,并由浏览器呈现。在ASP(Active Server Pages)应用程序的情况下(例如,我们在第三章开始时创建的应用程序),HTML文档可以由软件动态生成,而不是从存储在服务器上的文件中加载。

HTTP最初用于共享人类可读网页。然而,现在,HTTP请求可以返回描述应用程序中数据的XML或JSON格式的文档。

REST(REpresentational State Transfer)体系结构使用GET,放,HTTP的POST和DELETE操作允许客户端请求服务器在客户端-服务器应用程序中执行功能。用于与这些服务器和其他服务器通信的基本操作是发送网络请求向服务器执行HTML命令,现在我们要探索如何做到这一点。让我们看看三种与Web服务器交互的方法,并考虑它们的优缺点。这些是WebRequest,WebClient以及HttpClient。

WebREST

WebRequest类是一个抽象基类,用于指定Web请求的行为。它公开了一个名为Create的静态工厂方法,被赋予通用资源识别号(URI)指定要使用的资源的字符串。Create方法检查它给出的URI,并返回与该资源匹配的WebRequest类的子类。Create方法可以创建HttpWebRequest,FtpWebRequest,以及FileWebRequest对象。就网站而言,URI字符串将以““http”或““http:”Create方法将返回一个HttpWebRequest实例。

HttpWebRequest上的GetResponse方法返回描述来自服务器的响应的WebResponse实例。注意,此响应不是网页本身,而是描述来自服务器的响应的对象。为了从网页实际读取文本,程序必须在响应上使用GetResponseStream方法以获得可以从其中读取网页文本的流。清单4-13显示了它的工作原理。

列出4-13httpWebRequest

WebRequest webRequest=WebRequest.Create("https://www.microsoft.com”;WebResponse webResponse=webRequest.GetResponse();使用(StreamReader responseReader=new StreamReader(webResponse.GetResponseStream()){string siteText=responseReader.ReadToEnd();控制台.写线(siteText);}

注意,StreamReader周围的使用可以确保在读取了网页响应时关闭输入流。在使用之后显式关闭此流或WebResponse实例非常重要,否则,连接将不被重用,程序可能用完web连接。

使用WebRequest实例读取网页的工作原理,但是它相当复杂。确实如此,然而,具有这样的优点,即程序可以在web上设置广泛的属性,并请求根据特定的服务器需求对其进行定制。在我们将要考虑的其他一些方法中,这种灵活性是不可用的。

清单4-13中的代码是同步的,这样,程序将等待生成网页响应和读取响应。可以异步方式使用WebRequest,这样程序就不会以这种方式暂停。然而,当操作完成时,程序员必须创建要调用的事件处理程序。

网络客户端

这个网络客户端类提供了一种从web服务器读取文本的更简单和更快的方式。清单4-14显示了如何实现这一点。现在不需要创建流来读取页面内容(尽管如果愿意可以这样做),并且不需要在从服务器获得答复之前处理对web请求的响应。清单4-14显示了它的工作原理。

列出4-14WebClient

WebClient client=new WebClient();string siteText=client.DownloadString("http://www.microsoft.com”;控制台.写线(siteText);;

这个网络客户端类还提供了可用于从服务器异步读取的方法。清单4-15用于Windows演示基础(WPF)应用程序,以读取在窗口中显示的网页内容。

列出4-15个Web客户端异步

异步任务
          
           readWebpage(string uri){WebClient client=new WebClient();返回等待客户端。下载StringTaskAsync(uri);}
          

HTTP客户端

这个HTTP客户端之所以重要,是因为它是Windows通用应用程序下载网站内容的方式。不像WebREST以及网络客户端类,安HTTP客户端只提供异步方法。它可以以与网络客户端,如清单4-16所示。

列出4-16HttpClient

异步任务
          
           readWebpage(string uri){HttpClient client=new HttpClient();返回等待客户端.GetStringAsync(uri);}
          

异常处理

与文件处理一样,从因特网上载入信息容易出错。网络连接可能中断,或者服务器可能不可用。这意味着Web请求代码应该包含在适当的异常处理程序中。接下来的代码是清单4-16中的程序的一部分;它捕获异步加载方法引发的异常,并显示消息对话框包含错误信息。

尝试{string webText=await readWebpage(PageUriTextBox.Text);Text=webText;}.(Exception ex){var dialog=new MessageDialog(ex.Message,“请求失败”;等待对话框.ShowAsync();}

实现异步I/O操作

到目前为止,我们的示例程序中的所有文件输入/输出都是同步的调用方法执行文件操作的程序必须等待方法完成,然后才能移动到下一个语句。程序的用户必须等待文件操作完成,才能执行其他操作,这可能导致非常差的用户体验。

在第一章,技术熟练使用异步和等待,“您已经看到程序可以使用任务来执行方法的异步后台执行。在进一步阅读之前,有必要看一下这部分以重新理解这些元素。

文件类没有任何异步版本,所以文件流应该使用类。清单4-17显示了使用异步写入将字节数组写入指定文件的函数。

列表4-17异步文件写入

异步任务WriteBytesAsync(字符串文件名,byte[].){use(FileStream outStream=new FileStream(文件名,文件模式。OpenOrCreate,FileAccess.Write){waitingoutStream.WriteAsync(item,0,项目长度;}

演示程序是一个包含同步和异步文件写入方法的Windows演示基础应用程序。图4-5显示程序显示。用户可以同步或异步地向文件写入大量值,这取决于他们选择哪个启动按钮。他们还可以通过选择获得时间按钮,它将显示当前时间。当写入器的同步版本运行时,他们应该注意到,用户界面会在短时间内变得无响应。

图4-5

图4-5异步文件编写器演示

异步方法中的异常处理

如果异步文件写入方法引发任何异常,则必须捕获这些异常并为用户显示消息。只有在WriteBytesAsync方法返回任务WriteBytesAsync调用方法。清单4-18显示了一个按钮事件处理程序,它正确地执行了该操作,并捕获了文件写入操作可能引发的异常。

列出4-18文件异常

私有异步空白StartTaskButton_Click(对象发送器,RoutedEventArgs e){byte[]data=new byte[100];尝试{//注意,文件名包含无效字符,等待WriteBytesAsyncTask("演示:DAT“,数据);}.(Exception writeException){MessageBox.Show(writeException.Message,“文件写入失败”;}

当您运行清单4-18的示例程序时,该程序显示一个菜单,允许您在两个写方法之间进行选择;一个返回任务,另一个无效。两次写入都会抛出异常,但是只有返回Task的写才能正确捕获异常(参见图4-6以及显示消息框。另一个异常将无法正确处理。

图4-6

图4-6捕获文件异常

应该返回的唯一异步方法无效是windows控件的实际事件处理程序。所有其他异步方法必须返回结果或任务,这样就可以正确地处理方法抛出的任何异常。