[你必须知道的异步编程]——异步编程模型(APM)

本专题概要:

  • 引言

  • 你知道APM吗?

  • 你想知道如何使用异步编程模型编写代码吗?

  • 使用委托也可以实现异步编程,你知道否?

  • 小结


一、引言

  在前面的C#基础知识系列中介绍了从C#1.0——C#4.0中一些主要特性,然而.NET 4.5更新,除了提供了一些新的类和一些新的模板外,对于C#语言也做了一定的更新,最重要的就是.NET 4.5(对应于C#5.0)中提供了async和await两个关键字,这两个关键字是我们实现异步编程更加容易了,其实早在.NET 1.0开始微软就对异步编程做了相应的支持——即异步编程模型(APM), 之后在.NET 2.0中又提出了基于事件的异步编程模型(EAP),.NET 4.0中又提出了基于任务的异步编程模型(TAP)。所以为了帮助大家全面理解.NET类库对异步编程的支持,这里我把我学习异步编程的一些体会和理解分享出来,希望对大家在学习的过程中有所帮助。

  在开始讲解APM之前,我想先分享一下Visual Studio 版本、C# 版本和.NET 版本的一个对应关系。之所以在这里分享这个对应关系,是因为在C#基础知识系列的文章发布之后,有些初学者对.NET版本和C#语言特性之间的对应关系有点不清楚,有时候会弄混淆了。并且通过这个对应关系,也可以帮助大家对C#和.NET 类库有个全面的把控,可以帮助大家理清楚C#和.NET 类库中各个知识点,使他们可以对号入坐。具体他们的之间对应关系见下表:

C# 版本

.NET Framework版本

Visual Studio版本

发布日期

特性

C# 1.0

.NET Framework 1.0

Visual Studio .NET 2002

2002.1

委托

事件

APM

C# 1.1

.NET Framework 1.1

Visual Studio .NET 2003

2003.4

C# 2.0

.NET Framework 2.0

Visual Studio 2005(开始命名为Visual Studio)

2005.11

泛型

匿名方法

迭代器

可空类型

C# 3.0

.NET Framework 3.0

.NET Framework 3.5

Visual Studio 2008

2007.11

隐式类型的部变量

对象集合初始化

自动实现属性

匿名类型

扩展方法

查询表达式

Lambda表达式

表达式树

分部类和方法

Linq

C# 4.0

.NET Framework 4.0

Visual Studio 2010

2010.4

动态绑定

命名和可选参数

泛型的协变和逆变

互操作性

C# 5.0

.NET Framework 4.5

Visual Studio 2012

2012.8

异步和等待(async和await)

调用方信息(Caller Information)

二、你知道APM吗?

  APM即异步编程模型的简写(Asynchronous Programming Model),大家在写代码的时候或者查看.NET 的类库的时候肯定会经常看到和使用以BeginXXX和EndXXX类似的方法,其实你在使用这些方法的时候,你就再使用异步编程模型来编写程序。异步编写模型是一种模式,该模式允许用更少的线程去做更多的操作,.NET Framework很多类也实现了该模式,同时我们也可以自定义类来实现该模式,(也就是在自定义的类中实现返回类型为IAsyncResult接口的BeginXXX方法和EndXXX方法)另外委托类型也定义了BeginInvoke和EndInvoke方法,并且我们使用WSDL.exe和SvcUtil.exe工具来生成Web服务的代理类型时,也会生成使用了APM的BeginXxx和EndXxx方法。下面就具体就拿FileStream类的BeginReadEndRead方法来介绍下下异步编程模型的实现。

BeginXxx方法——开始执行异步操作介绍

当需要读取文件中的内容时,我们通常会采用FileStream的同步方法Read来读取,该同步方法的定义为:

// 从文件流中读取字节块并将该数据写入给定的字节数组中
// array代表把读取的字节块写入的缓存区
// offset代表array的字节偏量,将在此处读取字节
// count 代表最多读取的字节数
public override int Read(byte[] array, int offset, int count )

   该同步方法会堵塞执行的线程,当一个WinForm程序需要实现读取一个大文件的内容然后把内容显示在界面时,如果我们调用该方法去读取文件的内容时,此时Read方法会堵塞UI线程,在读取文件内容没有完成之前,用户不能对窗体进行任何的操作,包括关闭应用程序,此时用户看到的该窗体会出现无法响应,这样就给用户带来不好一个用户体验,从用户角度来看是用户体验不好,此时我们自己解决问题的思路肯定是——能不能让读取文件操作在另外一个线程中执行,这样就不会堵塞UI线程,这时候UI线程继续做属于自己的事情,即响应用户的操作。不错,微软也肯定也想到了这个解决方案的,并且在实际操作中也是这么做的,即通过BeginRead方法来实现异步编程,使读取操作不再堵塞UI线程BeginRead方法代表异步执行Read操作,并返回实现IAsyncResult接口的对象,该对象存储着异步操作的信息,下面就看下BeginRead方法的定义,看看与同步Read的方法区别在哪里的.

// 开始异步读操作
// 前面的3个参数和同步方法代表的意思一样,这里就不说了,可以看到这里多出了2个参数
// userCallback代表当异步IO操作完成时,你希望由一个线程池线程执行的方法,该方法必须匹配AsyncCallback委托
// stateObject代表你希望转发给回调方法的一个对象的引用,在回调方法中,可以查询IAsyncResult接口的AsyncState属性来访问该对象
public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject
)

从上面的代码中可以看出异步方法和同步方法的区别,如果你在使用该异步方法时,不希望异步操作完成后调用任何代码,你可以把userCallback参数设置为null。该异步方法子所以不会堵塞UI线程是因为调用该方法后,该方法会立即把控制权返回给调用线程(如果是UI线程来调用该方法时,即返回给UI线程),然而同步却不是这样,同步方法是等该操作完成之后返回读取的内容之后才返回给调用线程,从而导致在操作完成之前调用线程就一直等待状态。

EndXxx方法——结束异步操作介绍

  前面介绍完了BeginXxx方法,我们看到所有BeginXxx方法返回的都是实现了IAsyncResult接口的一个对象,并不是对应的同步方法所要得到的结果的。此时我们需要调用对应的EndXxx方法来结束异步操作,并向该方法传递IAsyncResult对象,EndXxx方法的返回类型就是和同步方法一样的。例如,FileStreamEndRead方法返回一个Int32来代表从文件流中实际读取的字节数。

对于访问异步操作的结果,APM提供了四种方式供开发人员选择:

  1. 在调用BeginXxx方法的线程上调用EndXxx方法来得到异步操作的结果,但是这种方式会阻塞调用线程,知道操作完成之后调用线程才继续运行

  2. 查询IAsyncResultAsyncWaitHandle属性,从而得到WaitHandle,然后再调用它的WaitOne方法来使一个线程阻塞并等待操作完成再调用EndXxx方法来获得操作的结果。

  3. 循环查询IAsyncResultIsComplete属性,操作完成后再调用EndXxx方法来获得操作返回的结果。

  4. 使用 AsyncCallback委托来指定操作完成时要调用的方法,在操作完成后调用的方法中调用EndXxx操作来获得异步操作的结果。

  在上面的4种方式中,第4种方式是APM的首选方式,因为此时不会阻塞执行BeginXxx方法的线程,然而其他三种都会阻塞调用线程,相当于效果和使用同步方法是一样,个人感觉根本失去了异步编程的特点,所以其他三种方式可以简单了解下,在实际异步编程中都是使用委托的方式。

  通过上面的介绍,大家应该对异步编程模型有了进一步的了解了吧,要识别某个类是否实现了异步编程模型,只需要看是不是有BeginXxx方法(当然返回类型需要是IAsyncResult)和EndXxx方法。其实异步编程模型这个模式,就是微软利用委托和线程池帮助我们实现的一个模式(该模式利用一个线程池线程去执行一个操作,在FileStream类BeginRead方法中就是执行一个读取文件操作,该线程池线程会立即将控制权返回给调用线程,此时线程池线程在后台进行这个异步操作;异步操作完成之后,通过回调函数来获取异步操作返回的结果。此时就是利用委托的机制。所以说异步编程模式时利用委托和线程池线程搞出来的模式,包括后面的基于事件的异步编程和基于任务的异步编程,还有C# 5中的async和await关键字,都是利用这委托和线程池搞出来的。他们的本质其实都是一样的,只是后面提出来的使异步编程更加简单罢了。)

既然这里讲到了FileStream对象,这里就提出一个关于该类值得注意的地方的:

FileStream对象默认情况下是同步打开操作系统句柄,当我们创建一个FileStream对象没有为其指定FileOptions.Asynchronous参数或者没有显示指定useAsync为true时,Windows 操作系统会以同步的方法执行所有的文件操作,即使此时你还是可以调用BeginRead方法。但是这样对于你的应用程序,操作只是表面上是异步执行的,但FileStream类在内部会用另一个线程模拟异步行为。

同样道理,当创建的FileStream对象指定了FileOptions.Asynchronous参数时,然后我们仍然可以调用Read同步方法,此时在内部,FileStream类会开始一个异步操作,并立即使调用线程进入睡眠状态,知道操作完成才会唤醒,通过这样来模拟同步行为。因此在使用FileStream对象时,需要先决定是同步执行还是异步执行。并显示地指定FileOptions.Asynchronous参数或useAsync参数。

三、你想知道如何使用异步编程模型编写代码吗?

  介绍了这么久的异步编程模型,大家肯定很迫不及待地想使用异步编程模型来改写自己的同步应用程序或者实现一个异步的应用程序。下面就通过一个例子来演示如何使用APM来现异步编程(该程序也实现了一个同步方法,为了让大家更好地体会同步线程和异步线程的区别,本程序的实现是一个控制台程序,大家也可以很好地一直与WinForm应用程序和WPF程序):

#region use APM to download file asynchronouslyprivate static void DownloadFileAsync(string url){try{// Initialize an HttpWebRequest objectHttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);// Create an instance of the RequestState and assign HttpWebRequest instance to its request field.RequestState requestState = new RequestState();requestState.request = myHttpWebRequest;myHttpWebRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), requestState);}catch (Exception e){Console.WriteLine("Error Message is:{0}",e.Message);}}// The following method is called when each asynchronous operation completes.private static void ResponseCallback(IAsyncResult callbackresult){// Get RequestState objectRequestState myRequestState = (RequestState)callbackresult.AsyncState;HttpWebRequest myHttpRequest = myRequestState.request;// End an Asynchronous request to the Internet resourcemyRequestState.response = (HttpWebResponse)myHttpRequest.EndGetResponse(callbackresult);// Get Response Stream from ServerStream responseStream = myRequestState.response.GetResponseStream();myRequestState.streamResponse = responseStream;IAsyncResult asynchronousRead = responseStream.BeginRead(myRequestState.BufferRead, 0, myRequestState.BufferRead.Length, ReadCallBack, myRequestState);      }// Write bytes to FileStreamprivate static void ReadCallBack(IAsyncResult asyncResult){try{// Get RequestState objectRequestState myRequestState = (RequestState)asyncResult.AsyncState;// Get Response Stream from ServerStream responserStream = myRequestState.streamResponse;//int readSize = responserStream.EndRead(asyncResult);if (readSize > 0){myRequestState.filestream.Write(myRequestState.BufferRead, 0, readSize);responserStream.BeginRead(myRequestState.BufferRead, 0, myRequestState.BufferRead.Length, ReadCallBack, myRequestState);}else{Console.WriteLine("\nThe Length of the File is: {0}", myRequestState.filestream.Length);Console.WriteLine("DownLoad Completely, Download path is: {0}", myRequestState.savepath);myRequestState.response.Close();myRequestState.filestream.Close();}     }catch (Exception e){Console.WriteLine("Error Message is:{0}", e.Message);}}#endregion

运行结果为(从运行结果也可以看出,在主线程中调用 DownloadFileAsync(downUrl)方法时,DownloadFileAsync(downUrl)方法中的myHttpWebRequest.BeginGetResponse调用被没有阻塞调用线程(即主线程),而是立即返回到主线程,是主线程后面的代码可以立即执行)

04172854-3e641edef2264537b73eb67bedf7fbdf.png

如果我们调用的是同步方法时,此时会堵塞主线程,直到文件的下载操作被完成之后主线程才继续执行后面的代码,下面是下载文件的同步方法:

#region Download File Synchrouslyprivate static void DownLoadFileSync(string url){// Create an instance of the RequestStateRequestState requestState=new RequestState();try{// Initialize an HttpWebRequest objectHttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);// assign HttpWebRequest instance to its request field.requestState.request = myHttpWebRequest;requestState.response = (HttpWebResponse)myHttpWebRequest.GetResponse();requestState.streamResponse = requestState.response.GetResponseStream();int readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);while (readSize > 0){requestState.filestream.Write(requestState.BufferRead, 0, readSize);readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);}Console.WriteLine("\nThe Length of the File is: {0}", requestState.filestream.Length);Console.WriteLine("DownLoad Completely, Download path is: {0}", requestState.savepath);}catch (Exception e){Console.WriteLine("Error Message is:{0}", e.Message);}finally{requestState.response.Close();requestState.filestream.Close();}}#endregion

使用同步方法下载文件的运行结果为(大家可以对照两个方式的结果就可以明显看出他们的区别了。):

04173123-f0ae12f1ed8e41b0a9e3d1e2e93f9439.png

四、使用委托也可以实现异步编程,你知道否?

  在前面的介绍中已经提到委托类型也会定义了BeginInvoke方法和EndInvoke方法,所以委托类型也实现了异步编程模型,所以可以使用委托的BeginInvokeEndInvoke方法来回调同步方法从而实现异步编程。因为调用委托的BeginInvoke方法来执行一个同步方法时,此时会使用线程池线程回调这个同步方法并立即返回到调用线程中,由于耗时操作在另外一个线程上运行,所以执行BeginInvoke方法的主线程就不会被堵塞。但是这里存在的一个问题时,因为同步方法在另外一个线程中执行的,然而我们怎么把同步方法执行的状态反应到UI界面上来呢?因为在GUI应用程序(包括Windows窗体,WPF和Silverlight)中,创建窗口的线程是唯一能够对那个窗口进行更新的线程,所以在执行同步方法的线程就不能对窗口中的控件进行操作,也就不能把方法允许的结果反应到窗体上了。这里有两种解决方案,一种是设置控件的CheckForIllegalCrossThreadCalls 属性为false,设置为false的意思代表允许跨线程调用,(这种方式虽然可以解决该问题,但是不推荐,因为它违背了.NET安全规范);第二种就是使用SynchronizationContext基类,该类记录着线程的同步上下文对象,我们可以通过在GUI线程中调用SynchronizationContext.Current属性来获得GUI线程的同步上下文,然后当线程池线程需要更新窗体时,可以调用保存的SynchronizationContext派生对象的Post方法(Post方法会将回调函数送到GUI线程的队列中,每个线程都有各自的操作队列的,线程的执行都是从这个队列中拿方法去执行),向Post方法传递要由GUI线程调用的方法(该方法的定义要匹配SendOrPostCallback委托的签名),还需要想Post方法传递一个要传给回调方法的参数。

4.1 使用委托实现更好的用户体验——不堵塞UI线程

虽然第一种方案是一种不推荐的方案,但是我觉得有些朋友还是不知道怎么实现的,所以在这部分就用具体的代码来实现下,并且该实现也可以与使用同步上下文对象的方式进行对比,这样大家就可以更加了解如何使用委托来进行异步编程了。下面就具体看实现代码吧:

View Code// 定义用来实现异步编程的委托private delegate string AsyncMethodCaller(string fileurl);public Mainform(){InitializeComponent();txbUrl.Text = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";// 允许跨线程调用// 实际开发中不建议这样做的,违背了.NET 安全规范CheckForIllegalCrossThreadCalls = false;}private void btnDownLoad_Click(object sender, EventArgs e){rtbState.Text = "Download............";if (txbUrl.Text == string.Empty){MessageBox.Show("Please input valid download file url");return;}AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);methodCaller.BeginInvoke(txbUrl.Text.Trim(), GetResult, null);}// 同步下载文件的方法// 该方法会阻塞主线程,使用户无法对界面进行操作// 在文件下载完成之前,用户甚至都不能关闭运行的程序。private string DownLoadFileSync(string url){// Create an instance of the RequestStateRequestState requestState = new RequestState();try{// Initialize an HttpWebRequest objectHttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);// assign HttpWebRequest instance to its request field.requestState.request = myHttpWebRequest;requestState.response = (HttpWebResponse)myHttpWebRequest.GetResponse();requestState.streamResponse = requestState.response.GetResponseStream();int readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);while (readSize > 0){requestState.filestream.Write(requestState.BufferRead, 0, readSize);readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);}// 执行该方法的线程是线程池线程,该线程不是与创建richTextBox控件的线程不是一个线程// 如果不把 CheckForIllegalCrossThreadCalls 设置为false,该程序会出现“不能跨线程访问控件”的异常return string.Format("The Length of the File is: {0}", requestState.filestream.Length) + string.Format("\nDownLoad Completely, Download path is: {0}", requestState.savepath);}catch (Exception e){return string.Format("Exception occurs in DownLoadFileSync method, Error Message is:{0}", e.Message);}finally{requestState.response.Close();requestState.filestream.Close();}}// 异步操作完成时执行的方法private void GetResult(IAsyncResult result){AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;// 调用EndInvoke去等待异步调用完成并且获得返回值// 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成string returnstring= caller.EndInvoke(result);//sc.Post(ShowState,resultvalue);rtbState.Text = returnstring;     }

运行的结果为:

04194654-4dc331037959450e9cf9a14a6d18da42.png

4.2 在线程中访问另一个线程创建的控件

这部分将使用同步上下文的方式来实现在线程池线程中如何更新GUI线程中窗体,因为在程序代码部分都有详细的解释,这里就直接贴代码了

public partial class MainForm : Form{// 定义用来实现异步编程的委托private delegate string AsyncMethodCaller(string fileurl);// 定义显示状态的委托private delegate void ShowStateDelegate(string value);private ShowStateDelegate showStateCallback;SynchronizationContext sc;public MainForm(){InitializeComponent();txbUrl.Text = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";showStateCallback = new ShowStateDelegate(ShowState);}private void btnDownLoad_Click(object sender, EventArgs e){rtbState.Text = "Download............";btnDownLoad.Enabled = false;if (txbUrl.Text == string.Empty){MessageBox.Show("Please input valid download file url");return;}AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);methodCaller.BeginInvoke(txbUrl.Text.Trim(), GetResult, null);// 捕捉调用线程的同步上下文派生对象sc = SynchronizationContext.Current;}// 同步下载文件的方法// 该方法会阻塞主线程,使用户无法对界面进行操作// 在文件下载完成之前,用户甚至都不能关闭运行的程序。private string DownLoadFileSync(string url){// Create an instance of the RequestStateRequestState requestState = new RequestState();try{// Initialize an HttpWebRequest objectHttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);// assign HttpWebRequest instance to its request field.requestState.request = myHttpWebRequest;requestState.response = (HttpWebResponse)myHttpWebRequest.GetResponse();requestState.streamResponse = requestState.response.GetResponseStream();int readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);while (readSize > 0){requestState.filestream.Write(requestState.BufferRead, 0, readSize);readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);}// 执行该方法的线程是线程池线程,该线程不是与创建richTextBox控件的线程不是一个线程// 如果不把 CheckForIllegalCrossThreadCalls 设置为false,该程序会出现“不能跨线程访问控件”的异常return string.Format("The Length of the File is: {0}", requestState.filestream.Length) + string.Format("\nDownLoad Completely, Download path is: {0}", requestState.savepath);}catch (Exception e){return string.Format("Exception occurs in DownLoadFileSync method, Error Message is:{0}", e.Message);}finally{requestState.response.Close();requestState.filestream.Close();}}// 异步操作完成时执行的方法private void GetResult(IAsyncResult result){AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;// 调用EndInvoke去等待异步调用完成并且获得返回值// 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成string returnstring = caller.EndInvoke(result);// 通过获得GUI线程的同步上下文的派生对象,// 然后调用Post方法来使更新GUI操作方法由GUI 线程去执行sc.Post(ShowState,returnstring);   }// 显示结果到richTextBox// 因为该方法是由GUI线程执行的,所以当然就可以访问窗体控件了private void ShowState(object result){rtbState.Text = result.ToString();btnDownLoad.Enabled = true;}}

程序的运行结果和前面使用第一方案的结果是一样的,这里就不重复贴图了,上面所有的实现都是部分代码,你可以在文章的最后下载本专题的所有源码。

五、小结

  到这里本专题关于异步编程模型的介绍就结束了,异步编程模型(APM)虽然是.NET 1.0中提出来的一个模式,相对于现在来说是旧了点,并且微软现在官方也表明在最新的代码中不推荐使用该模型来实现异步的应用程序,而是推荐使用基于任务的异步编程模型来实现异步的应用程序,但是我个人认为,正是因为它是.NET 1.0中提出的来,并且现在来看确实有些旧了, 所以我们才更应该好好研究下它,因为后面提出的EAP和TAP微软做了更多的封装,是我们对异步编程的本质都不清楚的(其实它们的本质都是使用线程池和委托机制的,具体可以查看前面的相关部分),并且系统学习下异步编程,也可以让我们对新的异步编程模型的所带来的好处有更可直观的认识。在后面的一专题我将带大家全面认识下基于事件的异步编程模型(EAP)。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/402757.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iOS 10 的一个重要更新-自定义的通知界面

续上篇,在简单闹钟的例子上,在通知界面上显示图片动画,并用通知关联的按钮更新通知界面。介绍 iOS 10 通知 API 的扩展:自定义通知显示界面。 新框架可以统一处理本地通知和远程推送,同时增加了一些新 API 来控制等待中…

[USACO 1.3.3]Calf Flac

o(︶︿︶)o 烦躁,看了半天没看懂这个O(n)的回文串算法是什么东西,直接套上模板就交了。然后AC了 题目: Description 据说如果你给无限只母牛和无限台巨型便携式电脑(有非常大的键盘),那么母牛们会制造出世上最棒的回文。你的工作就是去这些牛…

Class.forName()用法详解

Class.forName()用法详解 标签: classjvmjdbc数据库documentationjava2012-03-29 09:39 40414人阅读 评论(8) 收藏 举报分类:Java考古学(74) 主要功能 Class.forName(xxx.xx.xx)返回的是一个类 Class.forName(xxx.xx.xx)的作用是要…

应对不良网络文化的技术之一——网络信息抽取技术

1 引言 2008年1月17日,中国互联网络信息中心(CNNIC)发布了《第21次中国互联网络发展状况统计报告》[1],报告显示: (1) 截至2007年12月,网民数已增至2.1亿人。中国网民数增长迅速,比2007年6月增加4800万人&…

HBuilder完成webApp入门(2)

一、HBuilder的下载地址:http://www.dcloud.io/,点击那个“DownLoad”就可以 了 二、假设一切顺利,启动HBuilder后,大家会看到如下的界面 点击新建移动APP: 接下来就会弹出一个选择模板的对话框: 默认的模板…

高可用集群 heartbeatv1实例

——————— 高可用集群的简单配置 ————————地址规划 主节点:HA1 172.16.21.13 hostname node2.magedu.com备节点: HA2 172.16.21.14 hostname node1.magedu.comVIP 172.16.21.9前提工作1,配置主机名 hostname保证uname …

你知道“拉黑”、“关注”、“点赞”、“转发”、“分享到朋友圈”等英语咋说吗?

From: https://www.sohu.com/a/220161051_559507 “分享到朋友圈”等英语咋说吗? Mini apps 小程序 小程序”(mini apps)是一个不需要下载安装就可使用的应用(apps that can be accessed without downloading)&#x…

配套自测连载(三)

接上期(答案已给出)本期是专门针对《深入理解计算机网络》图书第4章而编写的10道计算机网络体系架构中的物理层技术自测题,可以检验你对本章的学习效果。把你的答案直接写在评论中即可,笔者将在每期发表10天后给出正确答案。本书是国内最通俗、最系统的计…

[json] JSON for Modern C++

有幸能接触到这个,这是我遇到的使用最方便的json了,效率没研究过! 简单了使用了下,感觉非常好用,记录下: 要使用这个json,只需要使用json.hpp就行,放入自己的工程里,但…

libinject的编译

libinject是一个Android进程注入实例,其下载地址为:http://download.csdn.net/download/ljhzbljhzb/3680780 libinject的编译需要NDK开发环境,在NDK安装成功之后,可以先将其自带的实例中的HelloJni导入到eclipse中,编译…

Linux Supervisor 守护进程基本配置

supervisor:C/S架构的进程控制系统,可使用户在类UNIX系统中监控、管理进程。常用于管理与某个用户或项目相关的进程。 组成部分supervisord:服务守护进程supervisorctl:命令行客户端Web Server:提供与supervisorctl功能相当的WEB操…

三阶魔方还原公式

From: https://www.cnblogs.com/zqifa/p/mofang-1.html 1. 第二层棱块归位: 2. 顶层十字 3. 顶层棱中间块归位 这一步的目的是使顶层的4个棱中间块全部归位。 转动顶层(U),若可以使一个棱中间块归位(如下图左,这里以[红-黄]块为例)&#x…

选项板概述

2019独角兽企业重金招聘Python工程师标准>>> 1、选项板概述 选项面板是一个包括一个或多个选项卡(Tab),同一时刻只显示一个选项卡的这种用户界面。比如下图的IE选项设置界面中,就是一个选项板的应用,选项板上有“常规”、“安全”…

三阶魔方的入门教程

From: http://www.rubik.com.cn/beginner.htm 下面是三阶魔方图文教程,想直接看更好懂的三阶魔方视频教程请点这里 魔方别看只有26个小方块,变化可真是不少,魔方总的变化数为 或者约等于4.31019。如果你一秒可以转3下魔方,不计重…

MySQL LIST分区(转载)

LIST分区和RANGE分区非常的相似,主要区别在于LIST是枚举值列表的集合,RANGE是连续的区间值的集合。二者在语法方面非常的相似。同样建议LIST分区列是非null列,否则插入null值如果枚举列表里面不存在null值会插入失败,这点和其它的…

啦啦

Y2错题解析 数据流程图描述信息的来龙去脉和实际流程,反映信息在系统中流动、处理和存储的情况。程序结构图用来描述程序结构,一般由构成系统的要素和表达要素间关系的连线或箭头构成。因果图是一种发现问题“根本原因”的分析方法。 Spring依赖检查的常…

emacs 入门教程,菜单汉化,配置文件等杂乱文章

首先来一发ArchWiki的Emacs简体中文的入门教程 https://wiki.archlinux.org/index.php/Emacs_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87) 怎样设置,Emacs中文菜单? 把包内的3个文件丢到 emacs/share/emacs/site-lisp下面。 在~/ 建一个 .emacs的…

关于element框架的el-image点击后,页面出现卡死等情况的解决方式

当el-image标签被添加时&#xff0c;页面的body就会自动添加style属性 所以我们绑定一个点击事件删除样式就可以了 <el-image style"width: 100px; height: 100px" :src"scope.row.logo" fit"scale-down" click"cancelStyle()"/&…

从源代码角度看Struts2返回JSON数据的原理

2019独角兽企业重金招聘Python工程师标准>>> 前面一篇文章其实只是介绍了如何在Struts2中返回JSON数据到客户端的具体范例而无关其原理&#xff0c;内容与标题不符惹来标题党嫌疑确实是笔者发文不够严谨&#xff0c;目前已修改标题&#xff0c;与内容匹配。本文将从…

一张图看懂encodeURI、encodeURIComponent、decodeURI、decodeURIComponent的区别

From:https://www.cnblogs.com/shuiyi/p/5277233.html 一、这四个方法的用处 1、用来编码和解码URI的 统一资源标识符&#xff0c;或叫做 URI&#xff0c;是用来标识互联网上的资源&#xff08;例如&#xff0c;网页或文件&#xff09;和怎样访问这些资源的传输协议&#xf…