2013编辑: async
和await
现在让这个变得微不足道! : - )
我有一些屏幕抓取网站的代码(仅用于说明目的!)
public System.Drawing.Image GetDilbert()
{
var dilbertUrl = new Uri(@"http://dilbert.com");
var request = WebRequest.CreateDefault(dilbertUrl);
string html;
using (var webResponse = request.GetResponse())
using (var receiveStream = webResponse.GetResponseStream())
using (var readStream = new StreamReader(receiveStream, Encoding.UTF8))
html = readStream.ReadToEnd();
var regex = new Regex(@"dyn/str_strip/[0-9/]+/[0-9]*\.strip\.gif");
var match = regex.Match(html);
if (!match.Success) return null;
string s = match.Value;
var groups = match.Groups;
if (groups.Count > 0)
s = groups[groups.Count - 1].ToString(); // the last group is the one we care about
var imageUrl = new Uri(dilbertUrl, s);
var imageRequest = WebRequest.CreateDefault(imageUrl);
using (var imageResponse = imageRequest.GetResponse())
using (var imageStream = imageResponse.GetResponseStream())
{
System.Drawing.Image image_ = System.Drawing.Image.FromStream(imageStream, true /*useEmbeddedColorManagement*/, true /*validateImageData*/);
return (System.Drawing.Image)image_.Clone(); // "You must keep the stream open for the lifetime of the Image."
}
}
现在,我想异步调用GetDilbert()。使用委托的简便方法:
Func<System.Drawing.Image> getDilbert;
IAsyncResult BeginGetDilbert(AsyncCallback callback, object state)
{
getDilbert = GetDilbert;
return getDilbert.BeginInvoke(callback, state);
}
System.Drawing.Image EndGetDilbert(IAsyncResult result)
{
return getDilbert.EndInvoke(result);
}
虽然这确实有效,但它不是很有效,因为委托线程将花费大部分时间等待两个I / O操作。
我想要做的是致电request.BeginGetResponse()
,进行正则表达式匹配,然后致电imageRequest.BeginGetResponse()
。所有这些都使用标准的异步调用模式并保留 BeginGetDilbert()和 EndGetDilbert()的签名。
我尝试了几种方法,对它们中的任何一种都没有完全满意;这似乎是一种皇室般的痛苦。因此,问题。 : - )
编辑:似乎使用迭代器的方法是frowned on by the C# compiler team。
编译团队的请求:
虽然确实如此 你可以使用迭代器来实现 国家机器,穷人协程, 等等,我希望别人不会这样做 所以。
请使用工具 这是他们的意图。如果你想 写状态机,写 你自己设计的图书馆 专门解决这个问题 问题,然后使用它。
将工具用于除以外的目的 他们的目的是什么 “聪明”,聪明是坏事;聪明的是 难以维护的程序 明白,聪明很难延伸, 聪明很难理解,聪明 让人们想到“开箱即用”; 那个盒子里有好东西。
使用Future<>
答案,因为它保留在C#中,与我的示例代码相同。不幸的是,TPL和F#都没有得到微软的官方支持......但是。
答案 0 :(得分:4)
实现这一目标真是一场噩梦。您需要创建回调以传递到每个'Begin'方法,然后运行方法的'continuation'。 (并且不要忘记确保所有异常处理和CompletedSynchronously逻辑都是正确的!)当你今天在C#中创建它时,你的代码变成了一堆无望的意大利面,但这是你实现目标的唯一方法(没有线程在I / O等待时阻塞。)
另一方面,如果它符合你的情况,F#使得这一点非常简单直接。有关概要,请参阅this video(即从52:20开始的8分钟)。
修改
回答Dan的评论,这是一个非常粗略的草图......我从我在outlook中写的一封电子邮件中删除它,我怀疑它是否编译。异常路径总是很粗糙,所以要小心(如果'cb'抛出怎么办?);你可能想在C#某处找到坚如磐石的AR / Begin / End实现(我不知道哪里,我肯定必须有很多)并将它用作模型,但这显示了要点。问题是,一旦你创作了一次,你就会一直拥有它; BeginRun和EndRun在任何F#异步对象上作为“开始/结束”工作。我们在F#bug数据库中建议在F#库的未来版本中在异步之上公开Begin / End APM,以便更容易使用传统C#代码中的F#异步计算。 (当然,我们正努力通过.Net 4.0中的并行任务库中的'任务'来更好地工作。)
type AR<’a>(o,mre,result) =
member x.Data = result
interface IAsyncResult with
member x.AsyncState = o
member x.AsyncWaitHandle = mre
member x.CompletedSynchronously = false
member x.IsCompleted = mre.IsSignalled
let BeginRun(a : Async<’a>, cb : AsyncCallback, o : obj) =
let mre = new ManualResetEvent(false)
let result = ref None
let iar = new AR(o,mre,result) :> IAsyncResult
let a2 = async {
try
let! r = a
result := Choice2_1(r)
with e ->
result := Choice2_2(e)
mre.Signal()
if cb <> null then
cb.Invoke(iar)
return ()
}
Async.Spawn(a2)
iar
let EndRun<’a>(iar) =
match iar with
| :? AR<’a> as ar ->
iar.AsyncWaitHandle.WaitOne()
match !(ar.Data) with
| Choice2_1(r) -> r
| Choice2_2(e) -> raise e
答案 1 :(得分:3)
public Image GetDilbert()
{
var dilbertUrl = new Uri(@"http://dilbert.com");
var request = WebRequest.CreateDefault(dilbertUrl);
var webHandle = new ManualResetEvent(false /* nonsignaled */);
Image returnValue = null;
request.BeginGetResponse(ar =>
{
//inside AsynchCallBack method for request.BeginGetResponse()
var response = (HttpWebResponse) request.EndGetResponse(ar);
string html;
using (var receiveStream = response.GetResponseStream())
using (var readStream = new StreamReader( receiveStream
, Encoding.UTF8))
{
html = readStream.ReadToEnd();
}
var re=new Regex(@"dyn/str_strip/[0-9/]+/[0-9]*\.strip\.gif");
var match=re.Match(html);
var imgHandle = new ManualResetEvent(true /* signaled */);
if (match.Success)
{
imgHandle.Reset();
var groups = match.Groups;
var s = (groups.Count>0) ?groups[groups.Count-1].ToString()
:match.Value;
var _uri = new Uri(dilbertUrl, s);
var imgReq = WebRequest.CreateDefault(_uri);
imgReq.BeginGetResponse(ar2 =>
{ var imageRsp= (HttpWebResponse)imgReq.EndGetResponse(ar2);
using (var imgStream=imageRsp.GetResponseStream())
{
var im=(Image)Image.FromStream(imgStream,true,true);
returnValue = (Image) im.Clone();
}
imgHandle.Set();
}, new object() /*state*/);
}
imgHandle.WaitOne();
webHandle.Set();
}, new object() /* state */);
webHandle.WaitOne();
return returnValue;
}
对于Begin / EndGetDilbert()方法,您可以使用http://blogs.msdn.com/pfxteam/archive/2008/02/29/7960146.aspx
中描述的Future<T>
技术
另见http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.begingetresponse.aspx
答案 2 :(得分:2)
你可能会发现Jeff Richter的AsyncEnumerator简化了一些事情。您可以在Wintellect PowerThreading库中获取它。
答案 3 :(得分:1)
毫无疑问:使用Concurrency and Coordination Runtime。它使用了许多上述技术,使您的代码比滚动自己的代码更简洁。