Mikuláš Poul
04/23/2026, 8:58 AMfuture: State[str | None] = some_task.submit(return_state=True)
if future.is_failed():
result = future.result(raise_on_failure=False)
if isinstance(result, SpecificError):
return Failed(message=str(result), name="SpecificError")
return result
result: str | None = future.result()
However I am getting errors getting the result when the future is not failed, I get errors like this
prefect.exceptions.UnfinishedRun: Run is in RUNNING state, its result is not available.
and
prefect.exceptions.UnfinishedRun: Run is in PENDING state, its result is not available.
How is this possible if the future is awaited when passing return_state=True? I am using prefect-dask to run tasksMarvin
04/23/2026, 8:58 AMMarvin
04/23/2026, 9:01 AMreturn_state=True on task.submit() does wait — but it also changes what you get back.
The key distinction is:
- some_task.submit() -> returns a future
- some_task.submit(return_state=True) -> returns a State, after waiting
So this line is suspicious:
future: State[str | None] = some_task.submit(return_state=True)
That variable is not a future anymore; it's a State.
Because of that, this logic:
if future.is_failed():
result = future.result(raise_on_failure=False)
...
result = future.result()
is actually calling State.result(), not PrefectFuture.result().
And State.result() will raise UnfinishedRun if the state is not final.
Why can that still happen with Dask?
With prefect-dask, there's likely a race/edge case where the wrapped Dask future has completed enough for Prefect to hand back a State, but that State may still be PENDING or RUNNING when you immediately call .result(). In other words, the wait happened at the future layer, but the state object you received is not guaranteed to be final in the way your code assumes.
So the short answer is:
return_state=True waits on the future, but you should not assume the returned State can always immediately yield a result via state.result() under prefect-dask.
A safer pattern is to not use return_state=True here.
Use the future directly:
future = some_task.submit()
try:
result: str | None = future.result()
return result
except SpecificError as exc:
return Failed(message=str(exc), name="SpecificError")
If you need to inspect the terminal state, do this instead:
future = some_task.submit()
state = future.wait()
if state.is_failed():
result = state.result(raise_on_failure=False)
if isinstance(result, SpecificError):
return Failed(message=str(result), name="SpecificError")
return result
return future.result()
Or, if you want to stay entirely state-based, guard against unfinished states:
state = some_task.submit(return_state=True)
if not state.is_final():
return Failed(message=f"Task returned non-final state: {state.type}", name="Unfinished")
if state.is_failed():
result = state.result(raise_on_failure=False)
if isinstance(result, SpecificError):
return Failed(message=str(result), name="SpecificError")
return result
return state.result()
A couple of practical notes:
- future.wait() returns a State
- future.result() waits for completion and gives you the task result
- state.result() only works if the State is final
So for your use case, the most robust approach is usually:
- use submit() without return_state=True
- use future.wait() if you want state inspection
- use future.result() for the actual value
Relevant references:
- Prefect task submission and futures: Tasks
- Prefect states: States
- `prefect-dask`: prefect-dask
If you want, I can help rewrite your exact snippet into a Dask-safe Prefect 3 pattern.