I was just very confused by some code that I wrote. I was surprised to discover that:
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
In addition to the explanation in the answers here, it can be helpful to go right to the source. It reaffirms the statement from another answer here that:
.map() gives results in the order they are submitted, whileFuture objects with concurrent.futures.as_completed() won't guarantee this ordering, because this is the nature of as_completed().map() is defined in the base class, concurrent.futures._base.Executor:
class Executor(object):
def submit(self, fn, *args, **kwargs):
raise NotImplementedError()
def map(self, fn, *iterables, timeout=None, chunksize=1):
if timeout is not None:
end_time = timeout + time.monotonic()
fs = [self.submit(fn, *args) for args in zip(*iterables)] #
As you mention, there is also .submit(), which left to be defined in the child classes, namely ProcessPoolExecutor and ThreadPoolExecutor, and returns a _base.Future instance that you need to call .result() on to actually make do anything.
The important lines from .map() boil down to:
fs = [self.submit(fn, *args) for args in zip(*iterables)]
fs.reverse()
while fs:
yield fs.pop().result()
The .reverse() plus .pop() is a means to get the first-submitted result (from iterables) to be yielded first, the second-submitted result to be yielded second, and so on. The elements of the resulting iterator are not Futures; they're the actual results themselves.