{"id":36833,"date":"2004-12-31T07:00:00","date_gmt":"2004-12-31T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2004\/12\/31\/using-fibers-to-simplify-enumerators-part-3-having-it-both-ways\/"},"modified":"2022-07-29T10:15:33","modified_gmt":"2022-07-29T17:15:33","slug":"using-fibers-to-simplify-enumerators-part-3-having-it-both-ways","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20041231-00\/?p=36833","title":{"rendered":"Using fibers to simplify enumerators, part 3: Having it both ways"},"content":{"rendered":"<p>As we discovered in the <a href=\"\/oldnewthing\/archive\/2004\/12\/29\/343664.aspx\"> previous two entries<\/a> [<a href=\"\/oldnewthing\/archive\/2004\/12\/30\/344281.aspx\">second<\/a>], the problem with enumeration is that somebody always loses.<\/p>\n<p>Now we will use fibers to fight back. Before you decide to use fibers in your programs, make sure to read the <strong>dire warnings<\/strong> at the end of this article. My goal here is to show one use of fibers, not to say that fibers are the answer to all your problems. Fibers can create more problems than they solve. We&#8217;ll come back to all the dire warnings later.<\/p>\n<p>As with most clever ideas, it has a simple kernel: Use a fiber to run both the caller and the enumerator each on their own stack.<\/p>\n<pre>#include &lt;windows.h&gt;\r\n#include &lt;shlwapi.h&gt;\r\n#include &lt;stdio.h&gt;\r\n#include &lt;strsafe.h&gt;\r\n\r\nenum FEFOUND {\r\n FEF_FILE,          \/\/ found a file\r\n FEF_DIR,           \/\/ found a directory\r\n FEF_LEAVEDIR,      \/\/ leaving a directory\r\n FEF_DONE,          \/\/ finished\r\n};\r\n\r\nenum FERESULT {\r\n FER_CONTINUE,      \/\/ continue enumerating\r\n                    \/\/ (if directory: recurse into it)\r\n FER_SKIP,          \/\/ skip directory (do not recurse)\r\n};\r\n\r\nclass __declspec(novtable) FiberEnumerator {\r\npublic:\r\n FiberEnumerator();\r\n ~FiberEnumerator();\r\n\r\n FEFOUND Next();\r\n void SetResult(FERESULT fer) { m_fer = fer; }\r\n void Skip() { SetResult(FER_SKIP); }\r\n\r\n virtual LPCTSTR GetCurDir() = 0;\r\n virtual LPCTSTR GetCurPath() = 0;\r\n virtual const WIN32_FIND_DATA* GetCurFindData() = 0;\r\n\r\nprotected:\r\n virtual void FiberProc() = 0;\r\n\r\n static void DECLSPEC_NORETURN WINAPI\r\n    s_FiberProc(void* pvContext);\r\n\r\n FERESULT Produce(FEFOUND fef);\r\n\r\nprotected:\r\n void* m_hfibCaller;\r\n void* m_hfibEnumerator;\r\n FEFOUND  m_fef;\r\n FERESULT m_fer;\r\n};\r\n\r\nFiberEnumerator::FiberEnumerator()\r\n : m_fer(FER_CONTINUE)\r\n{\r\n m_hfibEnumerator = CreateFiber(0, s_FiberProc, this);\r\n}\r\n\r\nFiberEnumerator::~FiberEnumerator()\r\n{\r\n DeleteFiber(m_hfibEnumerator);\r\n}\r\n\r\nvoid DECLSPEC_NORETURN FiberEnumerator::\r\n    s_FiberProc(void *pvContext)\r\n{\r\n FiberEnumerator* self =\r\n    reinterpret_cast&lt;FiberEnumerator*&gt;(pvContext);\r\n self-&gt;FiberProc();\r\n\r\n \/\/ Theoretically, we need only produce Done once,\r\n \/\/ but keep looping in case a consumer gets\r\n \/\/ confused and asks for the Next() item even\r\n \/\/ though we're Done.\r\n for (;;) self-&gt;Produce(FEF_DONE);\r\n}\r\n<\/pre>\n<p>This helper class does the basic bookkeeping of fiber-based enumeration. At construction, it remembers the fiber that is consuming the enumeration, as well as creating a fiber that will produce the enumeration. At destruction, it cleans up the fiber. The derived class is expected to implement the <code>FiberProc<\/code> method and call <code>Produce()<\/code> every so often.<\/p>\n<p>The real magic happens in the (somewhat anticlimactic) <code>Produce()<\/code> and <code>Next()<\/code> methods:<\/p>\n<pre>FERESULT FiberEnumerator::Produce(FEFOUND fef)\r\n{\r\n m_fef = fef; \/\/ for Next() to retrieve\r\n m_fer = FER_CONTINUE; \/\/ default\r\n SwitchToFiber(m_hfibCaller);\r\n return m_fer;\r\n}\r\n\r\nFEFOUND FiberEnumerator::Next()\r\n{\r\n m_hfibCaller = GetCurrentFiber();\r\n SwitchToFiber(m_hfibEnumerator);\r\n return m_fef;\r\n}\r\n<\/pre>\n<p>To <code>Produce()<\/code> something, we remember the production code, pre-set the enumeration result to its default of <code>FER_CONTINUE<\/code>, and switch to the consumer fiber. When the consumer fiber comes back with an answer, we return it from <code>Produce()<\/code>.<\/p>\n<p>To get the next item, we remember the identity of the calling fiber, then switch to the enumerator fiber. This runs the enumerator until it decides to <code>Produce()<\/code> something, at which point we take the production code and return it.<\/p>\n<p>That&#8217;s all there is to it. The <code>m_fef<\/code> and <code>m_fer<\/code> members are for passing the parameters and results back and forth across the fiber boundary.<\/p>\n<p>Okay, with that groundwork out of the way, writing the producer itself is rather anticlimactic.<\/p>\n<p>Since we want to make things easy for the consumer, we use <a href=\"\/oldnewthing\/archive\/2004\/12\/30\/344281.aspx\"> the interface the consumer would have designed<\/a>, with some assistance from the helper class.<\/p>\n<pre>class DirectoryTreeEnumerator : public FiberEnumerator {\r\npublic:\r\n DirectoryTreeEnumerator(LPCTSTR pszDir);\r\n ~DirectoryTreeEnumerator();\r\n\r\n LPCTSTR GetCurDir() { return m_pseCur-&gt;m_szDir; }\r\n LPCTSTR GetCurPath() { return m_szPath; }\r\n const WIN32_FIND_DATA* GetCurFindData()\r\n    { return &amp;m_pseCur-&gt;m_wfd; }\r\n\r\nprivate:\r\n void FiberProc();\r\n void Enum();\r\n\r\n struct StackEntry {\r\n   StackEntry* m_pseNext;\r\n   HANDLE m_hfind;\r\n   WIN32_FIND_DATA m_wfd;\r\n   TCHAR m_szDir[MAX_PATH];\r\n };\r\n bool Push(StackEntry* pse);\r\n void Pop();\r\n\r\nprivate:\r\n StackEntry *m_pseCur;\r\n TCHAR m_szPath[MAX_PATH];\r\n};\r\n\r\nDirectoryTreeEnumerator::\r\n DirectoryTreeEnumerator(LPCTSTR pszDir)\r\n : m_pseCur(NULL)\r\n{\r\n StringCchCopy(m_szPath, MAX_PATH, pszDir);\r\n}\r\n\r\nDirectoryTreeEnumerator::~DirectoryTreeEnumerator()\r\n{\r\n while (m_pseCur) {\r\n   Pop();\r\n }\r\n}\r\n\r\nbool DirectoryTreeEnumerator::\r\n      Push(StackEntry* pse)\r\n{\r\n pse-&gt;m_pseNext = m_pseCur;\r\n m_pseCur = pse;\r\n return\r\n  SUCCEEDED(StringCchCopy(pse-&gt;m_szDir,\r\n                 MAX_PATH, m_szPath)) &amp;&amp;\r\n  PathCombine(m_szPath, pse-&gt;m_szDir, TEXT(\"*.*\")) &amp;&amp;\r\n  (pse-&gt;m_hfind = FindFirstFile(m_szPath,\r\n       &amp;pse-&gt;m_wfd)) != INVALID_HANDLE_VALUE;\r\n}\r\n\r\nvoid DirectoryTreeEnumerator::Pop()\r\n{\r\n StackEntry* pse = m_pseCur;\r\n if (pse-&gt;m_hfind != INVALID_HANDLE_VALUE) {\r\n  FindClose(pse-&gt;m_hfind);\r\n }\r\n m_pseCur = pse-&gt;m_pseNext;\r\n}\r\n\r\nvoid DirectoryTreeEnumerator::FiberProc()\r\n{\r\n Enum();\r\n}\r\n\r\nvoid DirectoryTreeEnumerator::Enum()\r\n{\r\n StackEntry se;\r\n if (Push(&amp;se)) {\r\n  do {\r\n   if (lstrcmp(se.m_wfd.cFileName, TEXT(\".\")) != 0 &amp;&amp;\r\n       lstrcmp(se.m_wfd.cFileName, TEXT(\"..\")) != 0 &amp;&amp;\r\n       PathCombine(m_szPath, se.m_szDir, se.m_wfd.cFileName)) {\r\n    FEFOUND fef = (se.m_wfd.dwFileAttributes &amp;\r\n                    FILE_ATTRIBUTE_DIRECTORY) ?\r\n                    FEF_DIR : FEF_FILE;\r\n    if (Produce(fef) == FER_CONTINUE &amp;&amp; fef == FEF_DIR) {\r\n     Enum(); \/\/ recurse into the subdirectory we just produced\r\n    }\r\n   }\r\n  } while (FindNextFile(se.m_hfind, &amp;se.m_wfd));\r\n }\r\n Produce(FEF_LEAVEDIR);\r\n Pop();\r\n}\r\n<\/pre>\n<p>As you can see, this class is a mix of the two previous classes. Like the consumer-based class, information about the item being enumerated is obtained by calling methods on the enumerator object. But like the callback-based version, the loop that generates the objects themselves is a very simple recursive function, with a call to <code>Produce<\/code> in place of a callback.<\/p>\n<p>In fact, it&#8217;s even simpler than the callback-based version, since we don&#8217;t have to worry about the FER_STOP code. If the consumer wants to stop enumeration, the consumer simply stops calling <code>Next()<\/code>.<\/p>\n<p>Most of the complexity in the class is just bookkeeping to permit abandoning the enumeration prematurely.<\/p>\n<p>Okay, let&#8217;s take this fiber out for a spin. You can use the same <code>TestWalk<\/code> function as last time, but for added generality, change the first parameter from <code>DirectoryTreeEnumerator*<\/code> to <code>FiberEnumerator*<\/code>. (The significance of this will become apparent next time.)<\/p>\n<p>A little tweak needs to be made to the main function, though.<\/p>\n<pre>int __cdecl main(int argc, char **argv)\r\n{\r\n <span style=\"color: blue;\">ConvertThreadToFiber(NULL);<\/span>\r\n DirectoryTreeEnumerator e(TEXT(\".\"));\r\n TestWalk(&amp;e);\r\n <span style=\"color: blue;\">ConvertFiberToThread();<\/span>\r\n return 0;\r\n}\r\n<\/pre>\n<p>Since the enumerator is going to switch between fibers, we&#8217;d better convert the thread to a fiber so it&#8217;ll have something to switch back to!<\/p>\n<p>Here&#8217;s a schematic of what happens when you run this fiber-based enumerator:<\/p>\n<table cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td><code>ConvertThreadToFiber<\/code><\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<th style=\"border: solid .75pt black; border-bottom: none;\">Main fiber<\/th>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">construct <code>DirectoryTreeEnumerator<\/code><\/td>\n<td>&nbsp;<\/td>\n<th>Enumerator fiber<\/th>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>CreateFiber<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-bottom: none;\">(not running)<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">initialize variables<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Next(CONTINUE)<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>SwitchToFiber()<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">starts running<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>FindFirstFile<\/code> etc<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Produce(FILE)<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Next(CONTINUE)<\/code> &#8220;returns&#8221; FILE<\/td>\n<td>\u2190<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>SwitchToFiber()<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">use the result<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Next(CONTINUE)<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>SwitchToFiber()<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Produce(FILE)<\/code> &#8220;returns&#8221; CONTINUE<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>FindNextFile<\/code> etc<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Produce(DIR)<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Next(CONTINUE)<\/code> &#8220;returns&#8221; DIR<\/td>\n<td>\u2190<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>SwitchToFiber()<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">use the result<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Next(CONTINUE)<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>SwitchToFiber()<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Produce(DIR)<\/code> &#8220;returns&#8221; CONTINUE<\/td>\n<\/tr>\n<tr>\n<td colspan=\"3\" align=\"center\">and so on&#8230; until&#8230;<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Produce(DONE)<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>Next(CONTINUE)<\/code> &#8220;returns&#8221; DONE<\/td>\n<td>\u2190<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>SwitchToFiber()<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">cleanup<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none; border-bottom: none;\"><code>DeleteFiber<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid .75pt black; border-top: none;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid .75pt black; border-top: none;\">destruct <code>DirectoryTreeEnumerator<\/code><\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td><code>ConvertFiberToThread<\/code><\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Observe that from each fiber&#8217;s point of view, the other fiber is just a subroutine!<\/p>\n<p>Coding subtlety: Why do we capture the caller&#8217;s fiber each time the <code>Next()<\/code> method is called? Why not capture it when the <code>FiberEnumerator<\/code> is constructed?<\/p>\n<p>Next time, we&#8217;ll see how this fiber-based enumerator easily admits higher-order operations such as filtering and composition.<\/p>\n<p><strong>Dire warnings about fibers<\/strong><\/p>\n<p>Fibers are like dynamite. Mishandle them and your process explodes.<\/p>\n<p>The first dire warning is that fibers are expensive in terms of address space, since each one gets its own stack (typically a megabyte).<\/p>\n<p>And since each fiber has its own stack, it also has its own exception chain. This means that if a fiber throws an exception, only that fiber can catch it. (Same as threads.) That&#8217;s a strong argument against using an STL std::stack object to maintain our state: STL is based on an exception-throwing model, but you can&#8217;t catch exceptions raised by another fiber. (You also can&#8217;t throw exceptions past a COM boundary, which severely limits how much you can use STL in a COM object.)<\/p>\n<p>One of the big problems with fibers is that everybody has to be in cahoots. You need to decide on one person who will call <a href=\"http:\/\/msdn.microsoft.com\/library\/en-us\/dllproc\/base\/convertthreadtofiber.asp\"> the <code>ConvertThreadToFiber<\/code> function<\/a> since fiber\/thread conversion is not reference-counted. If two people call ConvertThreadToFiber on the same thread, the first will convert it, and so will the second! This results in two fibers for the same thread, and things can only get worse from there.<\/p>\n<p>You might think, &#8220;Well, wouldn&#8217;t <a href=\"http:\/\/msdn.microsoft.com\/library\/en-us\/dllproc\/base\/getcurrentfiber.asp\"> the <code>GetCurrentFiber<\/code> function<\/a> return NULL if the thread hasn&#8217;t been converted to a fiber?&#8221; Try it: It returns garbage. (It&#8217;s amazing how many people ask questions without taking even the slightest steps towards figuring out the answer themselves. <a href=\"\/oldnewthing\/archive\/2004\/07\/27\/198410.aspx#198429\"> Try writing<\/a> <a href=\"\/oldnewthing\/archive\/2004\/07\/20\/188696.aspx#188816\"> a test program<\/a>.)<\/p>\n<p>But even if GetCurrentFiber told you whether or not the thread had been converted to a fiber, that still won&#8217;t help. Suppose two people want to do fibrous activity on the thread. The first converts, the second notices that the thread is already a fiber (somehow) and skips the conversion. Now the first operation completes and calls <a href=\"http:\/\/msdn.microsoft.com\/library\/en-us\/dllproc\/base\/convertfibertothread.asp\"> the <code>ConvertFiberToThread<\/code> function<\/a>. Oh great, now the second operation is stranded doing fibrous activity without a fiber!<\/p>\n<p>Therefore, you can use fibers safely only if you control the thread and can get all your code to agree on who controls the fiber\/thread conversion.<\/p>\n<p>An important consequence of the &#8220;in cahoots&#8221; rule is that you have to make sure all the code you use on a fiber is &#8220;fiber-safe&#8221; &#8211; a level of safety even beyond thread-safety. The C runtime library keeps information in per-thread state: There&#8217;s <a href=\"http:\/\/msdn.microsoft.com\/library\/en-us\/vccore98\/html\/_crt__doserrno.2c_.errno.2c_._sys_errlist.2c_.and__sys_nerr.asp\"> errno<\/a>, all sorts of bonus bookkeeping when you <a href=\"http:\/\/msdn.microsoft.com\/library\/en-us\/vclib\/html\/_crt__beginthread.2c_._beginthreadex.asp\"> create a thread<\/a>, or call various functions that maintain state in per-thread data (such as <a href=\"http:\/\/msdn.microsoft.com\/library\/en-us\/vccore98\/html\/_crt_strerror.2c_._strerror.asp\">strerror<\/a>, <a href=\"http:\/\/msdn.microsoft.com\/library\/en-us\/vccore98\/html\/_crt__fcvt.asp\"> _fcvt<\/a>, and <a href=\"http:\/\/msdn.microsoft.com\/library\/en-us\/vccore98\/html\/_crt_strtok.2c_.wcstok.2c_._mbstok.asp\"> strtok<\/a>).<\/p>\n<p>In particular, <strong>C++ exception handling is managed by the runtime<\/strong>, and the runtime tracks this data in per-thread state (rather than per-fiber state). Therefore, if you throw a C++ exception from a fiber, <strong>strange things happen<\/strong>.<\/p>\n<p>(Note: Things may have changed in the C runtime lately; I&#8217;m operating from information that&#8217;s a few years old.)<\/p>\n<p>Even if you carefully avoid the C runtime library, you still have to worry about any other libraries you use that use per-thread data. None of them will work with fibers. If you see a call to <a href=\"http:\/\/msdn.microsoft.com\/library\/en-us\/dllproc\/base\/tlsalloc.asp\"> the <code>TlsAlloc<\/code> function<\/a>, then there&#8217;s a good chance that the library is not fiber-safe. (The fiber-safe version is <a href=\"http:\/\/msdn.microsoft.com\/library\/en-us\/dllproc\/base\/flsalloc.asp\"> the <code>FlsAlloc<\/code> function<\/a>.)<\/p>\n<p>Another category of things that are not fiber-safe are windows. Windows have thread affinity, not fiber affinity.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As we discovered in the previous two entries [second], the problem with enumeration is that somebody always loses. Now we will use fibers to fight back. Before you decide to use fibers in your programs, make sure to read the dire warnings at the end of this article. My goal here is to show one [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-36833","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>As we discovered in the previous two entries [second], the problem with enumeration is that somebody always loses. Now we will use fibers to fight back. Before you decide to use fibers in your programs, make sure to read the dire warnings at the end of this article. My goal here is to show one [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/36833","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=36833"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/36833\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=36833"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=36833"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=36833"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}