pywinrt / python-winsdk

Python package with bindings for Windows SDK
https://python-winsdk.readthedocs.io
MIT License
74 stars 8 forks source link

DataWriter.store_async is not really an async call? #21

Closed n0h4rt closed 1 year ago

n0h4rt commented 1 year ago

So, I want to make a function to write bytes into RandomAccessStream.

import asyncio

import winsdk.windows.storage.streams as streams

def Await(awaitable):
    '''Awaits awaitable call outside async function'''
    async def wrapper(awaitable):
        return await awaitable
    return asyncio.run(wrapper(awaitable))

def bytesToRAS(bytes_):
    '''Converts byte array into RandomAccessStream'''
    stream = streams.InMemoryRandomAccessStream()
    dataWriter = streams.DataWriter(stream)
    dataWriter.write_bytes(bytes_)

    # commit
    Await(dataWriter.store_async())  # did not work

    # flush? unsure is it neccesary?
    Await(dataWriter.flush_async())  # worked

    # detach the stream
    dataWriter.detach_stream()

    return stream

I got an error in this line Await(dataWriter.store_async()), TypeErrorobject _winsdk_Windows_Storage_Streams.DataWriterStoreOperation can't be used in 'await' expression. This one is worked Await(dataWriter.flush_async()).

I tried await dataWriter.store_async() got the same error. Then I tried dataWriter.store_async() directly it worked. bytes are correctly written to the stream. Why does it have this behavior?

Note: Any improvements in my code are welcome.

dlech commented 1 year ago

Why does it have this behavior?

The appears to be a defect in the bindings generator. It does not take into consideration that there are types like DataWriterStoreOperation that inherit from IAsyncOperation. I will look into it.

A potential workaround might be to await IAsyncOperation._from(dataWriter.store_async()).

Any improvements in my code are welcome.

A general improvement is to only run one asyncio loop at the top level of the program.

import asyncio

async def main():
    ...
    await ...

asyncio.run(main())

Otherwise you will have major performance issues and potential issues with things failing because of using different run loops.

dlech commented 1 year ago

If you don't want to use asyncio, you can wrap async methods in a concurrent.futures.Future.

import ctypes
from concurrent import futures
import winrt.windows.foundation as wf

def wait(op: wf.IAsyncOperation):
    op_future = futures.Future()

    def callback(operation: wf.IAsyncOperation, status: wf.AsyncStatus):
        if status == wf.AsyncStatus.COMPLETED:
            op_future.set_result(operation.get_results())
        elif status == wf.AsyncStatus.CANCELED:
            op_future.cancel()
        elif status == wf.AsyncStatus.ERROR:
            op_future.set_exception(ctypes.WinError(operation.error_code.value))

    op.completed = callback

    futures.wait([op_future])

    return op_future.result()
n0h4rt commented 1 year ago

Thanks for your quick response!

A general improvement is to only run one asyncio loop at the top level of the program.

Unfortunately my entire program is threading based. I have no time to rewrite it. Besides, all my async/await are just winsdk async calls.

wrap async methods in a concurrent.futures.Future

I think this is what I'm looking for. Your example works like a charm.

Thank you again!

dlech commented 1 year ago

Should be fixed in v1.0.0b8.