上一篇这边进行了一些结构上的设想,主要的核心内容就是消息和单线程实现.
这篇就介绍下如何通过C#中yield关键字,达到单线程执行多任务实现.
首先了解下yield的使用..
public static IEnumerable<object> YieldTest()
{
int x = 0;
x++;
yield return x;
x++;
yield return x;
}
这段简单的代码,反编译看下..发现一条编译创建的类(<YieldTest>d_3)
private sealed class <YieldTest>d__3 : IEnumerable<object>, IEnumerable, IEnumerator<object>, IEnumerator, IDisposable
{
// Fields
private int <>1__state;
private object <>2__current;
private int <>l__initialThreadId;
public int <x>5__4;
// Methods
[DebuggerHidden]
public <YieldTest>d__3(int <>1__state)
{
this.<>1__state = <>1__state;
this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
}
private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<x>5__4 = 0;
this.<x>5__4++;
this.<>2__current = this.<x>5__4;
this.<>1__state = 1;
return true;
case 1:
this.<>1__state = -1;
this.<x>5__4++;
this.<>2__current = this.<x>5__4;
this.<>1__state = 2;
return true;
case 2:
this.<>1__state = -1;
break;
}
return false;
}
[DebuggerHidden]
IEnumerator<object> IEnumerable<object>.GetEnumerator()
{
if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
{
this.<>1__state = 0;
return this;
}
return new Program.<YieldTest>d__3(0);
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return this.System.Collections.Generic.IEnumerable<System.Object>.GetEnumerator();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
void IDisposable.Dispose()
{
}
// Properties
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
}
找到原有类:
public static IEnumerable<object> YieldTest()
{
int iteratorVariable0 = 0;
iteratorVariable0++;
yield return iteratorVariable0;
iteratorVariable0++;
yield return iteratorVariable0;
}
查看IL部分
.method public hidebysig static class [mscorlib]System.Collections.Generic.IEnumerable`1<object> YieldTest() cil managed
{
.maxstack 2
.locals init (
[0] class WindNet.Program/<YieldTest>d__3 d__,
[1] class [mscorlib]System.Collections.Generic.IEnumerable`1<object> enumerable)
L_0000: ldc.i4.s -2
L_0002: newobj instance void WindNet.Program/<YieldTest>d__3::.ctor(int32)
L_0007: stloc.0
L_0008: ldloc.0
L_0009: stloc.1
L_000a: br.s L_000c
L_000c: ldloc.1
L_000d: ret
}
创建刚才多出来的那个类实例 然后返回.
看下MoveNext部分:
private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<x>5__4 = 0;
this.<x>5__4++;
this.<>2__current = this.<x>5__4;
this.<>1__state = 1;
return true;
case 1:
this.<>1__state = -1;
this.<x>5__4++;
this.<>2__current = this.<x>5__4;
this.<>1__state = 2;
return true;
case 2:
this.<>1__state = -1;
break;
}
return false;
}
比较下我们原来的代码,
我们有2个也就是返回1,和返回2
每个状态在MoveNext中都对应1_state这个状态,
也就是,在底层实现中,每次都传入一个当前状态指针,然后执行对应的步骤.
这个也就是我们执行多任务的关键,虽然我们可以手写MoveNext结构,
但是这从调用和编写上都不友好,既然这里提供了语法糖那么我们善加利用就可以写出比较简单的代码了.
我们简单的从web上抓取2个网址这样一个例子来演示如何在一个线程中"同时"完成2个任务.
public static IEnumerable<string> Html1Get1()
{
WebClient wc = new WebClient();
bool done = false;
string html = "";
int x = 0;
wc.DownloadStringCompleted += (sender, e) =>
{
done = true;
html = e.Result;
};
wc.DownloadStringAsync(new Uri("http://www.baidu.com"));
while (!done)
{
x++;
Console.WriteLine("Html1Get1:" + x);
yield return null;
}
yield return html;
}
public static IEnumerable<string> Html1Get2()
{
WebClient wc = new WebClient();
bool done = false;
string html = "";
int x = 0;
wc.DownloadStringCompleted += (sender, e) =>
{
done = true;
html = e.Result;
};
wc.DownloadStringAsync(new Uri("http://www.baidu.com"));
while (!done)
{
x++;
Console.WriteLine("Html1Get2:" + x);
yield return null;
}
yield return html;
}
static void Main(string[] args)
{
var x1 = Html1Get1().GetEnumerator();
var x2 = Html1Get2().GetEnumerator();
while (x1.MoveNext() || x2.MoveNext())
{
x1.MoveNext();
x2.MoveNext();
}
Console.WriteLine(x1.Current.Substring(0,1000));
Console.WriteLine(x2.Current.Substring(0, 1000));
}
从输出结果上我们可以看出,2部分的代码虽然说不上同时运行,但是会被交替运行.
1执行一会 2执行一会,而且在同一个线程中,也就是达到我们多任务的要求..
进行下调用封装后,写起来和同步单线程没什么区别,而且,没有让人头疼的回调.
至于如何避免过程中那个While,在设计逻辑上其实也是可以避免的.