When a pair of BeginXxx()/EndXxx()
calls appear in the code, this is acceptable. But what if the algorithm requires several such calls in a row, then the number of methods (or anonymous delegates) will multiply and the code will become less readable. Fortunately, this problem is solved in both F # and C #.
So, imagine that you want to download a web page in a fully asynchronous mode and save it on your hard disk. For this you need
A naive solution to a problem looks like this:
static void Main( string [] args)<br/>
{<br/>
Program p = new Program();<br/>
//
p.DownloadPage( "http://habrahabr.ru" );<br/>
// 10.
p.waitHandle.WaitOne(10000);<br/>
}<br/>
<br/>
//
private WebRequest wr;<br/>
private FileStream fs;<br/>
private AutoResetEvent waitHandle = new AutoResetEvent( false );<br/>
<br/>
//
public void DownloadPage( string url)<br/>
{<br/>
wr = WebRequest.Create(url);<br/>
wr.BeginGetResponse(AfterGotResponse, null );<br/>
}<br/>
<br/>
//
private void AfterGotResponse(IAsyncResult ar)<br/>
{<br/>
var resp = wr.EndGetResponse(ar);<br/>
var stream = resp.GetResponseStream();<br/>
var reader = new StreamReader(stream);<br/>
string html = reader.ReadToEnd();<br/>
// true
fs = new FileStream( @"c:\temp\file.htm" , FileMode.CreateNew,<br/>
FileAccess.Write, FileShare.None, 1024, true );<br/>
var bytes = Encoding.UTF8.GetBytes(html);<br/>
//
fs.BeginWrite(bytes, 0, bytes.Length, AfterDoneWriting, null );<br/>
}<br/>
<br/>
// , wait handle
private void AfterDoneWriting(IAsyncResult ar)<br/>
{<br/>
fs.EndWrite(ar);<br/>
fs.Flush();<br/>
fs.Close();<br/>
waitHandle.Set();<br/>
}<br/>
This solution still flowers. Imagine, for example, that you additionally need to download and save asynchronously all the pictures, and only when they are all saved open the folder in which they were recorded. Using the paradigm above, this is a real nightmare.
The first thing you can do is group the pieces of functionality into anonymous delegates [ 1 ]. Then we get something like this:
private AutoResetEvent waitHandle = new AutoResetEvent( false );<br/>
public void DownloadPage( string url)<br/>
{<br/>
var wr = WebRequest.Create(url);<br/>
wr.BeginGetResponse(ar =><br/>
{<br/>
var resp = wr.EndGetResponse(ar);<br/>
var stream = resp.GetResponseStream();<br/>
var reader = new StreamReader(stream);<br/>
string html = reader.ReadToEnd();<br/>
var fs = new FileStream( @"c:\temp\file.htm" , FileMode.CreateNew,<br/>
FileAccess.Write, FileShare.None, 1024, true );<br/>
var bytes = Encoding.UTF8.GetBytes(html);<br/>
fs.BeginWrite(bytes, 0, bytes.Length, ar1 =><br/>
{<br/>
fs.EndWrite(ar1);<br/>
fs.Flush();<br/>
fs.Close();<br/>
waitHandle.Set();<br/>
}, null );<br/>
}, null );<br/>
}<br/>
This solution looks good, but if the call chain is really large, the code will not be readable and difficult to manage. This is especially true when, for example, the execution of a chain needs to be suspended and canceled.
Workflow is an F # construct. The idea is approximately the same - you define a certain block in which some operators (such as let
, for example) are redefined. Asynchronous workflow is such a workflow within which the operators ( let!
do!
And others) are redefined so that these operators allow you to “wait” for the operation to complete. That is, when we write
async {<br/>
â‹®<br/>
let ! x = Y()<br/>
â‹®<br/>
}<br/>
this means that we make a call to BeginYyy()
for Y
, and when the result is available, we write the result in x
.
Thus, the analogue of downloading and writing a file to F # will look something like this:
// /
// F#
type WebRequest with <br/>
member x.GetResponseAsync() =<br/>
Async.BuildPrimitive(x.BeginGetResponse, x.EndGetResponse)<br/>
let private DownloadPage(url:string) =<br/>
async {<br/>
try <br/>
let r = WebRequest.Create(url)<br/>
let ! resp = r.GetResponseAsync() // let!
use stream = resp.GetResponseStream()<br/>
use reader = new StreamReader(stream)<br/>
let html = reader.ReadToEnd()<br/>
use fs = new FileStream( @"c:\temp\file.htm" , FileMode.Create,<br/>
FileAccess.Write, FileShare.None, 1024, true );<br/>
let bytes = Encoding.UTF8.GetBytes(html);<br/>
do ! fs.AsyncWrite(bytes, 0, bytes.Length) //
with <br/>
| :? WebException -> ()<br/>
}<br/>
// , -
Async.RunSynchronously(DownloadPage( "http://habrahabr.ru" ))<br/>
Using clever syntactic constructions, F # allows us using specially created "primitives" (such as GetResponseAsync()
and AsyncWrite()
) to make calls with Begin / End semantics, but without dividing them into individual methods or delegates. Oddly enough, you can do about the same thing in C #.
Jeffrey Richter, the well-known author of the book CLR via C #, is also the author of the PowerThreading library. This library [ 2 ] provides a number of interesting features, one of which is an implementation of the asynchronous workflow analogue in C #.
This is done very simply - we have a certain “manager of tokens” called AsyncEnumerator
. This class actually allows you to interrupt the execution of the method and continue it again. How can I interrupt the execution of a method? This is done using a simple yield return
.
Using AsyncEnumerator
easy. We take and add it as a parameter to our method, and also change the return value to IEnumerator<int>
:
public IEnumerator< int > DownloadPage( string url, AsyncEnumerator ae )<br/>
{<br/>
â‹®<br/>
}<br/>
Next, we write code using BeginXxx()/EndXxx()
, using three simple rules:
ae.End()
as a callback parameter.IAsyncResult
gets ae.DequeueAsyncResult()
yield return X
, where X is the number of operations started.This is what our download method looks like when using AsyncEnumerator
:
public IEnumerator< int > DownloadPage( string url, AsyncEnumerator ae)<br/>
{<br/>
var wr = WebRequest.Create(url);<br/>
wr.BeginGetResponse(ae.End(), null );<br/>
yield return 1;<br/>
var resp = wr.EndGetResponse(ae.DequeueAsyncResult());<br/>
var stream = resp.GetResponseStream();<br/>
var reader = new StreamReader(stream);<br/>
string html = reader.ReadToEnd();<br/>
using ( var fs = new FileStream( @"c:\temp\file.htm" , FileMode.Create,<br/>
FileAccess.Write, FileShare.None, 1024, true ))<br/>
{<br/>
var bytes = Encoding.UTF8.GetBytes(html);<br/>
fs.BeginWrite(bytes, 0, bytes.Length, ae.End(), null );<br/>
yield return 1;<br/>
fs.EndWrite(ae.DequeueAsyncResult());<br/>
}<br/>
}<br/>
As you can see, the asynchronous method is now recorded in a synchronous form - we even managed to use using
for the file stream. The code has become more readable, unless of course additional returns yield returns are considered.
It now remains only to call this method:
static void Main()<br/>
{<br/>
Program p = new Program();<br/>
var ae = new AsyncEnumerator();<br/>
ae.Execute(p.DownloadPage( "http://habrahabr.ru" , ae));<br/>
} <br/>
The problem of chains of asynchronous calls was not so terrible. For simple situations, anonymous delegates will do; for complex ones, there are asynchronous workflows and AsyncEnumerator
, depending on which language is closer to you.
Chains are simple, but what to do with whole dependency graphs? About this - in the next post. â–
Source: https://habr.com/ru/post/71625/
All Articles