"yield return" 来自事件处理程序
"yield return" from event handler
我有一个 class,它在构造函数中接受一个流。然后可以设置各种事件的回调,然后调用StartProcessing
。问题是我想从一个应该 return 和 IEnumerable
.
的函数中使用它
示例:
public class Parser
{
public Parser(System.IO.Stream s) { // saves stream and does some set up }
public delegate void OnParsedHandler(List<string> token);
public event OnParsedHandler OnParsedData;
public void StartProcessing()
{
// reads stream and makes callback when it has a whole record
}
}
public class Application
{
public IEnumerable<Thing> GetThings(System.IO.Stream s)
{
Parser p = new Parser(s);
p.OnParsedData += (List<string> str) =>
{
Thing t = new Thing(str[0]);
// here is where I would like to yield
// but I can't
yield return t;
};
p.StartProcessing();
}
}
现在我的解决方案(不是很好)是将所有事物放入由 lambda 捕获的列表中,然后在调用 StartProcessing 后迭代它们。
public class Application
{
public IEnumerable<Thing> GetThings(System.IO.Stream s)
{
Parser p = new Parser(s);
List<Thing> thingList = new List<Thing>();
p.OnParsedData += (List<string> str) =>
{
Thing t = new Thing(str[0]);
thingList .Add(t);
};
p.StartProcessing();
foreach(Thing t in thingList )
{
yield return t;
}
}
}
这里的问题是现在我必须将所有 Thing
对象保存到列表中。
你在这里遇到的问题是你根本没有 "pull" 机制,你试图从解析器推送数据。如果解析器要将数据推送给你,而不是让调用者拉取数据,那么 GetThings
应该 return 一个 IObservable
,而不是 IEnumerable
,所以调用者可以在数据就绪时使用它。
如果这里有一个拉机制真的很重要,那么 Parser
不应该触发一个事件来表明它有新数据,而是调用者应该能够向它请求新数据并且让它得到它;它应该 return 所有已解析的数据,或者它本身 return 一个 IEnumerable
.
有趣的问题。我想以 @servy 就 push and pull 所说的内容为基础。在上面的实现中,您有效地将推送机制调整为拉接口。
现在,要事第一。您尚未指定对 StartProcessing()
方法的调用是否为阻塞调用。对此有几点评论:
如果该方法是阻塞的(同步的),那么无论如何都没有必要将其调整为拉模型。调用者将看到在单个 blocking 调用中处理的所有数据。
在这方面,通过事件处理程序间接接收数据分散到两个看似无关的构造中,否则应该是一个单一的、内聚的、显式的操作。例如:
void ProcessAll(Action<Thing> callback);
另一方面,如果 StartProcessing()
方法实际上产生了一个新线程(可能更好地命名为 BeginProcessing()
并遵循 Event-based Asynchronous Pattern or another async processing pattern), you could adapt it to a pull machanism by means of a synchronization construct using a wait handle: ManualResetEvent
、互斥锁等。伪代码:
public IEnumerable<Thing> GetThings(System.IO.Stream s)
{
var parser = new Parser(s);
var waitable = new AutoResetEvent(false);
Thing item = null;
parser.OnParsedData += (Thing thing) =>
{
item = thing;
waitable.Set();
};
IAsyncResult result = parser.BeginProcessing();
while (!result.IsCompleted)
{
waitable.WaitOne();
yield return item;
}
}
免责声明
以上代码仅作为表达思路之用。它不是线程安全的,并且同步机制无法正常工作。有关详细信息,请参阅 producer-consumer 模式。
我有一个 class,它在构造函数中接受一个流。然后可以设置各种事件的回调,然后调用StartProcessing
。问题是我想从一个应该 return 和 IEnumerable
.
示例:
public class Parser
{
public Parser(System.IO.Stream s) { // saves stream and does some set up }
public delegate void OnParsedHandler(List<string> token);
public event OnParsedHandler OnParsedData;
public void StartProcessing()
{
// reads stream and makes callback when it has a whole record
}
}
public class Application
{
public IEnumerable<Thing> GetThings(System.IO.Stream s)
{
Parser p = new Parser(s);
p.OnParsedData += (List<string> str) =>
{
Thing t = new Thing(str[0]);
// here is where I would like to yield
// but I can't
yield return t;
};
p.StartProcessing();
}
}
现在我的解决方案(不是很好)是将所有事物放入由 lambda 捕获的列表中,然后在调用 StartProcessing 后迭代它们。
public class Application
{
public IEnumerable<Thing> GetThings(System.IO.Stream s)
{
Parser p = new Parser(s);
List<Thing> thingList = new List<Thing>();
p.OnParsedData += (List<string> str) =>
{
Thing t = new Thing(str[0]);
thingList .Add(t);
};
p.StartProcessing();
foreach(Thing t in thingList )
{
yield return t;
}
}
}
这里的问题是现在我必须将所有 Thing
对象保存到列表中。
你在这里遇到的问题是你根本没有 "pull" 机制,你试图从解析器推送数据。如果解析器要将数据推送给你,而不是让调用者拉取数据,那么 GetThings
应该 return 一个 IObservable
,而不是 IEnumerable
,所以调用者可以在数据就绪时使用它。
如果这里有一个拉机制真的很重要,那么 Parser
不应该触发一个事件来表明它有新数据,而是调用者应该能够向它请求新数据并且让它得到它;它应该 return 所有已解析的数据,或者它本身 return 一个 IEnumerable
.
有趣的问题。我想以 @servy 就 push and pull 所说的内容为基础。在上面的实现中,您有效地将推送机制调整为拉接口。
现在,要事第一。您尚未指定对 StartProcessing()
方法的调用是否为阻塞调用。对此有几点评论:
如果该方法是阻塞的(同步的),那么无论如何都没有必要将其调整为拉模型。调用者将看到在单个 blocking 调用中处理的所有数据。
在这方面,通过事件处理程序间接接收数据分散到两个看似无关的构造中,否则应该是一个单一的、内聚的、显式的操作。例如:
void ProcessAll(Action<Thing> callback);
另一方面,如果 StartProcessing()
方法实际上产生了一个新线程(可能更好地命名为 BeginProcessing()
并遵循 Event-based Asynchronous Pattern or another async processing pattern), you could adapt it to a pull machanism by means of a synchronization construct using a wait handle: ManualResetEvent
、互斥锁等。伪代码:
public IEnumerable<Thing> GetThings(System.IO.Stream s)
{
var parser = new Parser(s);
var waitable = new AutoResetEvent(false);
Thing item = null;
parser.OnParsedData += (Thing thing) =>
{
item = thing;
waitable.Set();
};
IAsyncResult result = parser.BeginProcessing();
while (!result.IsCompleted)
{
waitable.WaitOne();
yield return item;
}
}
免责声明
以上代码仅作为表达思路之用。它不是线程安全的,并且同步机制无法正常工作。有关详细信息,请参阅 producer-consumer 模式。