<jupyter_start><jupyter_text>How to manage concurrency`labbench` includes simplified concurrency support for this kind of I/O-constrained operations like waiting for instruments to perform long operations. It is not suited for parallelizing CPU-intensive tasks because the operations share a single process on one CPU core, instead of multiprocessing, which may be able to spread operations across multiple CPU cores.  ExampleHere are very fake functions that just use `time.sleep` to block. They simulate longer instrument calls (such as triggering or acquisition) that take some time to complete.Notice that `do_something_3` takes 3 arguments (and returns them), and that `do_something_4` raises an exception.<jupyter_code>import time

def do_something_1 ():
    print('start 1')
    time.sleep(1)
    print('end 1')
    return 1

def do_something_2 ():
    print('start 2')
    time.sleep(2)
    print('end 2')
    return 2

def do_something_3 (a,b,c):
    print('start 3')
    time.sleep(2.5)
    print('end 3')
    return a,b,c 

def do_something_4 ():
    print('start 4')
    time.sleep(3)
    raise ValueError('I had an error')
    print('end 4')
    return 4

def do_something_5 ():
    print('start 5')
    time.sleep(4)
    raise IndexError('I had a different error')
    print('end 5')
    return 4<jupyter_output><empty_output><jupyter_text>Here is the simplest example, where we call functions `do_something_1` and `do_something_2` that take no arguments and raise no exceptions:<jupyter_code>import labbench as lb

results = lb.concurrently(do_something_1, do_something_2)
print(f'results: {results}')<jupyter_output>start 1
start 2
end 1
end 2<jupyter_text>We can also pass functions by wrapping the functions in `Call()`, which is a class designed for this purpose:<jupyter_code>results = lb.concurrently(do_something_1, lb.Call(do_something_3, 1,2,c=3))
results<jupyter_output>start 1
start 3
end 1
end 3<jupyter_text>More than one of the functions running concurrently may raise exceptions. Tracebacks print to the screen, and by default `ConcurrentException` is also raised:<jupyter_code>from labbench import concurrently, Call

results = concurrently(do_something_4, do_something_5)
results<jupyter_output>start 4
start 5
Traceback (most recent call last):
  File "<ipython-input-1-73606d5b193d>", line 24, in do_something_4
    raise ValueError('I had an error')
ValueError: I had an error
Traceback (most recent call last):
  File "<ipython-input-1-73606d5b193d>", line 31, in do_something_5
    raise IndexError('I had a different error')
IndexError: I had a different error
Traceback (most recent call last):
  File "<ipython-input-1-73606d5b193d>", line 24, in do_something_4
    raise ValueError('I had an error')
ValueError: I had an error
Traceback (most recent call last):
  File "<ipython-input-1-73606d5b193d>", line 31, in do_something_5
    raise IndexError('I had a different error')
IndexError: I had a different error<jupyter_text>the `catch` flag changes concurrent exception handling behavior to return values of functions that did not raise exceptions (instead of raising `ConcurrentException`). The return dictionary only includes keys for functions that did not raise exceptions.<jupyter_code>from labbench import concurrently, Call

results = concurrently(do_something_4, do_something_1, catch=True)
results<jupyter_output>start 4
start 1
end 1
Traceback (most recent call last):
  File "<ipython-input-1-73606d5b193d>", line 24, in do_something_4
    raise ValueError('I had an error')
ValueError: I had an error
{'fraction_non_alphanumeric': 0.08070866141732283, 'fraction_numerical': 0.03346456692913386, 'mean_word_length': 5.143350604490501, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '151497', 'license': 'Apache-2.0', 'n_tokens_mistral': 1154, 'n_tokens_neox': 1071, 'n_words': 407, 'path': 'examples/concurrency.ipynb', 'repo_name': 'usnistgov/gpsdata', 'size': '3556'}