Use the synchronous Python SDK from asyncio

By solvethree3 min read

Use asyncio.to_thread to run the synchronous Solvethree.solve method without blocking your event loop. Put a semaphore around the offload and create a separate client inside each worker call. The SDK has no native async client; its requests and polling still run synchronously in the thread.

On this page

Bound the work and set the SDK deadline

The example requires Python 3.10+ and the official Python SDK. Set SOLVETHREE_API_KEY and SOLVETHREE_PROXY in your server environment. Replace accounts.example.com and login with your target hostname and action. The site must already have an active profile available to your workspace; see site profile setup.

asyncio · bound the synchronous SDK work
import asyncio
import os
import uuid
from solvethree import Solvethree

async def main():
    limit = asyncio.Semaphore(4)
    async def solve_one(number):
        task = {"site": "accounts.example.com", "action": "login", "proxy": os.environ["SOLVETHREE_PROXY"], "requestId": str(uuid.uuid4())}
        def blocking_solve():
            return Solvethree(os.environ["SOLVETHREE_API_KEY"]).solve(task, timeout=180)
        async with limit:
            try:
                result = await asyncio.to_thread(blocking_solve)
                print("Ready task:", result["id"])
                # Submit the solution now with its proxy and browser identity.
            except Exception as error:
                print("Job", number, "requestId", task["requestId"], "error class", type(error).__name__)
                # Retain task and inspect SDK recovery IDs before restarting it.
    await asyncio.gather(*(solve_one(number) for number in range(8)))

asyncio.run(main())
# Canceling an await does not stop an already-running thread or remote task.
  • The semaphore admits four jobs at a time from the eight submitted to gather. Jobs waiting for a slot haven't started their SDK call.
  • Each worker constructs its own Solvethree client rather than sharing one between threads.
  • The SDK's timeout=180 supplies the solve deadline. Time spent waiting for the semaphore isn't included. An asyncio timeout around the await is not a substitute for this SDK deadline.
  • The task and its requestId are created before entering the worker. Keep both available for recovery rather than generating a replacement ID whenever an error occurs.

The SDK creates the task once and polls until it is ready or raises an error. You don't need to write a second polling loop around solve.

Cancellation doesn't stop the worker or remote task

Canceling the coroutine awaiting to_thread does not stop an already-running thread or cancel the remote solve. The sample's ordinary exception handler is not cancellation cleanup.

This also affects the concurrency limit: cancellation can release the semaphore while the worker continues. If you immediately admit replacement jobs, more than four SDK calls can remain active. If you need a strict limit during cancellation, track outstanding workers and keep their capacity occupied until they finish.

For production use, retain the exact task inputs and request ID somewhere your recovery path can access after the coroutine exits. A timeout means you stopped receiving a result, not necessarily that task creation failed.

  1. 1.

    Recover a known task

    Inspect the Python SDK error's task_id and request_id. If you have a task ID, resume retrieval with get_task rather than creating another task.

  2. 2.

    Recover an interrupted creation

    If you don't have a task ID, replay the identical task inputs with the original requestId. That returns the existing task if creation succeeded. Changing the inputs while reusing that ID conflicts.

  3. 3.

    Separate failure from interruption

    A TaskFailedError means the task reached terminal failed status. Inspect its errorCode and error; don't keep polling it or blindly retry unchanged invalid inputs. HTTP errors are a separate failure layer.

Submit the solution when the worker returns

The sample prints the ready task ID; it doesn't submit anything to the target. Replace the comment after that print with your application-specific submission, and use the solution promptly rather than waiting for every job in gather to finish.

Submit the returned solution with its userAgent, secChUa, and the same proxy used for solving. The SDK's successful_proxy helper selects the proxy from the original task and ready result. A ready result means a solution was generated—not that your application request succeeded.

Run your first task.

Start with $5 in workspace credit.

Create a workspace