You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add support for async lambda in RegisterTableFunction #226
(Discussed in Discord. Formalizing as an Issue so someone can work on it. Is a nice-to-have.)
The following code using an async lambda within RegisterTableFunction does not compile under v1.1.3:
voidTestTableFn(){usingvarduckDBConnection=newDuckDBConnection("Data Source=:memory:");duckDBConnection.Open();
#pragma warning disable
duckDBConnection.RegisterTableFunction<string,int>("my_people",async(parameters)=>{stringstartingName=parameters[0].GetValue<string>();intstartingAge=parameters[1].GetValue<int>();varresult=awaitGetRandomPersons(startingName,startingAge).ConfigureAwait(false);// needs to be://var result = GetRandomPersons(startingName, startingAge).ConfigureAwait(false).GetAwaiter().GetResult();returnnewTableFunction([newColumnInfo("PersonName",typeof(string)),newColumnInfo("PersonAge",typeof(int))],result);},(item,writers,rowIndex)=>{varperson=itemasPerson;writers[0].WriteValue(person?.Name,rowIndex);writers[1].WriteValue(person?.Age,rowIndex);});
#pragma warning restore
usingvarcmd=duckDBConnection.CreateCommand();cmd.CommandText="SELECT * FROM my_people('Alice', 25);";varreader=cmd.ExecuteReader();}publicrecordPerson(stringName,intAge);publicstaticasyncTask<List<Person>>GetRandomPersons(stringstartingName,intstartingAge){Randomrandom=newRandom();vardata=newList<Person>();for(inti=0;i<10;i++){data.Add(newPerson($"{startingName}{i+1:D2}",startingAge+random.Next(1,50)));awaitTask.Delay(100);// Simulate asynchronous operation}returndata;}
The error is
CS4010 Cannot convert async lambda expression
to delegate type 'Func<IReadOnlyList<IDuckDBValueReader>, TableFunction>'.
An async lambda expression may return void, Task or Task<T>,
none of which are convertible to 'Func<IReadOnlyList<IDuckDBValueReader>, TableFunction>'.
Giorgi mentioned it is a limitation of the C api within which the callback is executed and maybe that is a hard limit. But perhaps someone can find an implementation that allows for non-blocking awaits.
It doesn't work at runtime though. DuckDB throws this error:
INTERNAL Error: Failed to bind "github_search": Table function must return at least one column
This error signals an assertion failure within DuckDB. This usually occurs due to unexpected conditions or errors in the program's logic.
The reason it's happening is that Bind callback is now async void so it returns control back to the native code when it hits first await:
One workaround that I can think of (but I don't like it) is making the Bind callback void and using the same GetAwaiter().GetResult() mechanism to invoke the resultCallback. This feels like hiding the problem under the rug and creating a false impression that the resultCallback is truly async.
I haven't looked at the source code yet. I keep forgetting this is interop with C, not C++.
In the meantime I wrote a demo of how to use the legacy Asynchronous Programming Model (APM) (pre async) to invoke an otherwise Task Asynchronous Pattern (async/await) signature.
So this is at the consuming side rather than your api side. I'm sure you remember how we were all told not to block on asynchronous calls when async/await first came out. David Fowler
(But if you did, they said .GetAwaiter().GetResult() was the safest pattern.)
That said, and I am no internals expert, but I think the IAsyncResult interface is designed so that after BeginInvoke is called, the threads are managed in such a way that the the main thread is not blocked, whereas I think.GetAwaiter().GetResult() is pure blocking. Maybe it's no different, dunno. In any case, the demo is one possible pattern for consumers to invoke async table-value-functions until the api can be sorted out.
Adding my 2 cents, I've also trying to utilize RegisterTableFunction and an interface of my data sources is also async. By the nature of DuckDB library I guess true async handlers are not possible.
In fact my case is even more complicated because data reader is pushed to a callback, smth like
To use this API with synchronous RegisterTableFunction - which is divided on 2 synchronous handlers - I'll have to run an additional (concurrent) task to simulate async readHandler that communicates with these 2 TVF handlers using several TaskCompletionSource instances. It seems this Frankenstein even works ^_^
Activity
Giorgi commentedon Nov 13, 2024
Fixing compilation errors is easy: https://github.com/Giorgi/DuckDB.NET/tree/Table-Function-Async-Callback
It doesn't work at runtime though. DuckDB throws this error:
The reason it's happening is that
Bindcallback is nowasync voidso it returns control back to the native code when it hits first await:DuckDB.NET/DuckDB.NET.Data/DuckDBConnection.TableFunction.cs
Line 122 in 3902db8
One workaround that I can think of (but I don't like it) is making the Bind callback void and using the same
GetAwaiter().GetResult()mechanism to invoke theresultCallback.This feels like hiding the problem under the rug and creating a false impression that theresultCallbackis truly async.mldisibio commentedon Nov 17, 2024
I haven't looked at the source code yet. I keep forgetting this is interop with
C, notC++.In the meantime I wrote a demo of how to use the legacy
Asynchronous Programming Model (APM)(preasync) to invoke an otherwiseTask Asynchronous Pattern(async/await) signature.Gist: asyncTvhDemo
So this is at the consuming side rather than your api side. I'm sure you remember how we were all told not to block on asynchronous calls when
async/awaitfirst came out. David Fowler(But if you did, they said
.GetAwaiter().GetResult()was the safest pattern.)That said, and I am no internals expert, but I think the
IAsyncResultinterface is designed so that afterBeginInvokeis called, the threads are managed in such a way that the the main thread is not blocked, whereas I think.GetAwaiter().GetResult()is pure blocking. Maybe it's no different, dunno. In any case, the demo is one possible pattern for consumers to invoke async table-value-functions until the api can be sorted out.VitaliyMF commentedon Nov 25, 2024
Adding my 2 cents, I've also trying to utilize
RegisterTableFunctionand an interface of my data sources is also async. By the nature of DuckDB library I guess true async handlers are not possible.In fact my case is even more complicated because data reader is pushed to a callback, smth like
To use this API with synchronous
RegisterTableFunction- which is divided on 2 synchronous handlers - I'll have to run an additional (concurrent) task to simulate async readHandler that communicates with these 2 TVF handlers using severalTaskCompletionSourceinstances. It seems this Frankenstein even works ^_^Giorgi commentedon Nov 25, 2024
I have come to the same conclusion.