Skip to content

Commit 9fc7ebc

Browse files
committed
- Renamed RawPython.InterpolationHandler to RawPython.CodeInterpolator
- Avoid making RawPython.CodeInterpolator ref struct as a temporary workaround for #3 - The coroutine awaiter now also propagate exceptions to C#-side - AsyncIOCoroutineAwaiter now throw exception(s) in task. - Implemented AsyncIOCoroutineAwaiter<Ret>
1 parent 2f073f9 commit 9fc7ebc

5 files changed

Lines changed: 151 additions & 23 deletions

File tree

PythonNETExtensions/AsyncIO/AsyncIOCore.cs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
using System;
12
using System.Runtime.CompilerServices;
23
using System.Threading.Tasks;
4+
using Python.Runtime;
35
using PythonNETExtensions.Core;
6+
using PythonNETExtensions.Core.Handles;
47
using PythonNETExtensions.Modules;
58

69
namespace PythonNETExtensions.AsyncIO
@@ -60,19 +63,49 @@ void SetupAndRunLoop()
6063
}
6164
}
6265

66+
private static void HandleAsyncExceptions(TaskCompletionSource tcs, PyObject exception)
67+
{
68+
try
69+
{
70+
using (new PythonHandle())
71+
{
72+
RawPython.Run($"throw {exception:py};");
73+
}
74+
}
75+
76+
catch (Exception ex)
77+
{
78+
tcs.SetException(ex);
79+
}
80+
81+
// ThrowLastAsClrException(null);
82+
//
83+
// [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = nameof(ThrowLastAsClrException))]
84+
// static extern Exception ThrowLastAsClrException(PythonException @class);
85+
}
86+
6387
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6488
private static dynamic GenerateAwaiter(dynamic taskCompletionSource, dynamic coroutine, bool hasReturn)
6589
{
66-
const string METHOD_NAME = "generated_awaiter", RESULT_VAR_NAME = "result";
90+
const string METHOD_NAME = "generated_awaiter",
91+
RESULT_VAR_NAME = "result",
92+
TASK_COMPLETION_SOURCE_VAR_NAME = "tcs",
93+
EXCEPTION_VAR_NAME = "exception";
6794

68-
var awaiter = RawPython.Run<dynamic>(
95+
var codegen = (RawPython.InterpolationHandler)
6996
$"""
7097
async def {METHOD_NAME}():
71-
{RESULT_VAR_NAME} = await {(object) coroutine:py};
72-
{(object) taskCompletionSource:py}.{nameof(taskCompletionSource.SetResult)}({(hasReturn ? RESULT_VAR_NAME : string.Empty)});
73-
98+
try:
99+
{TASK_COMPLETION_SOURCE_VAR_NAME} = {taskCompletionSource:py};
100+
{RESULT_VAR_NAME} = await {coroutine:py};
101+
{TASK_COMPLETION_SOURCE_VAR_NAME}.{nameof(TaskCompletionSource.SetResult)}({(hasReturn ? RESULT_VAR_NAME : string.Empty)});
102+
except Exception as {EXCEPTION_VAR_NAME}:
103+
{(object) HandleAsyncExceptions:py}({TASK_COMPLETION_SOURCE_VAR_NAME}, {EXCEPTION_VAR_NAME});
104+
74105
return {METHOD_NAME}();
75-
""");
106+
""";
107+
108+
var awaiter = RawPython.Run<dynamic>(codegen);
76109

77110
return awaiter;
78111
}

PythonNETExtensions/AsyncIO/AsyncIOCoroutineAwaiter.cs

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Runtime.CompilerServices;
33
using System.Threading.Tasks;
4-
using PythonNETExtensions.Core;
4+
using PythonNETExtensions.Core.Handles;
55

66
namespace PythonNETExtensions.AsyncIO
77
{
@@ -33,7 +33,7 @@ public void UnsafeOnCompleted(Action continuation)
3333
var handle = Handle;
3434
handle.Handle.Dispose();
3535

36-
CoroutineTask.ContinueWith(_ =>
36+
CoroutineTask.ContinueWith(task =>
3737
{
3838
handle.Handle = new PythonHandle();
3939
continuation();
@@ -45,8 +45,71 @@ public AsyncIOCoroutineAwaiter GetAwaiter()
4545
{
4646
return this;
4747
}
48+
49+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
50+
public void GetResult()
51+
{
52+
var task = CoroutineTask;
4853

54+
if (task.IsFaulted)
55+
{
56+
throw task.Exception;
57+
}
58+
}
59+
}
60+
61+
public struct AsyncIOCoroutineAwaiter<RetT>: ICriticalNotifyCompletion
62+
{
63+
private readonly Task<RetT> CoroutineTask;
64+
65+
private readonly AsyncPythonHandle Handle;
66+
67+
internal AsyncIOCoroutineAwaiter(Task<RetT> coroutineTask, AsyncPythonHandle handle)
68+
{
69+
CoroutineTask = coroutineTask;
70+
Handle = handle;
71+
}
72+
73+
public bool IsCompleted
74+
{
75+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
76+
get => CoroutineTask.IsCompleted;
77+
}
78+
79+
public void OnCompleted(Action continuation)
80+
{
81+
throw new NotImplementedException();
82+
}
83+
84+
public void UnsafeOnCompleted(Action continuation)
85+
{
86+
var handle = Handle;
87+
handle.Handle.Dispose();
88+
89+
CoroutineTask.ContinueWith(task =>
90+
{
91+
handle.Handle = new PythonHandle();
92+
continuation();
93+
});
94+
}
95+
96+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
97+
public AsyncIOCoroutineAwaiter<RetT> GetAwaiter()
98+
{
99+
return this;
100+
}
101+
49102
[MethodImpl(MethodImplOptions.AggressiveInlining)]
50-
public void GetResult() { }
103+
public RetT GetResult()
104+
{
105+
var task = CoroutineTask;
106+
107+
if (task.IsFaulted)
108+
{
109+
throw task.Exception;
110+
}
111+
112+
return task.Result;
113+
}
51114
}
52115
}

PythonNETExtensions/Core/RawPython.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public Object(dynamic item, string name = null)
2525
}
2626

2727
[InterpolatedStringHandler]
28-
public ref struct InterpolationHandler
28+
public struct InterpolationHandler
2929
{
3030
[ThreadStatic]
3131
private static StringBuilder _StringBuilder;
@@ -115,7 +115,13 @@ public void AppendFormatted<T>(T item)
115115
}
116116

117117
[MethodImpl(MethodImplOptions.AggressiveInlining)]
118-
public void AppendFormatted<T>(T item, string format)
118+
public void AppendFormatted(dynamic item, string format)
119+
{
120+
AppendFormatted<dynamic>(item, format);
121+
}
122+
123+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
124+
public void AppendFormatted<T>(dynamic item, string format)
119125
{
120126
if (format == "py")
121127
{

PythonNETExtensions/Modules/AsyncIOModule.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.Runtime.CompilerServices;
2-
using System.Threading.Tasks;
32
using PythonNETExtensions.AsyncIO;
4-
using PythonNETExtensions.Core;
3+
using PythonNETExtensions.Core.Handles;
54

65
namespace PythonNETExtensions.Modules
76
{
@@ -38,9 +37,9 @@ public AsyncIOCoroutineAwaiter RunCoroutine(dynamic coroutine, AsyncPythonHandle
3837
return new AsyncIOCoroutineAwaiter(AsyncIOCore.CoroutineToTask(coroutine), handle);
3938
}
4039

41-
public Task<RetT> RunCoroutine<RetT>(dynamic coroutine)
40+
public AsyncIOCoroutineAwaiter<RetT> RunCoroutine<RetT>(dynamic coroutine, AsyncPythonHandle handle)
4241
{
43-
return AsyncIOCore.CoroutineToTask<RetT>(coroutine);
42+
return new AsyncIOCoroutineAwaiter<RetT>(AsyncIOCore.CoroutineToTask(coroutine), handle);
4443
}
4544

4645
public dynamic Sleep(int durationInSeconds) => Module.sleep(durationInSeconds);

SampleCode/Program.cs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading.Tasks;
44
using PythonNETExtensions.Config;
55
using PythonNETExtensions.Core;
6+
using PythonNETExtensions.Core.Handles;
67
using PythonNETExtensions.Modules;
78
using PythonNETExtensions.Modules.PythonBuiltIn;
89
using PythonNETExtensions.Versions;
@@ -43,7 +44,7 @@ private static async Task Sample()
4344
var result = RawPython.Run<string>(
4445
$"""
4546
print({HELLO_WORLD_TEXT:py});
46-
return {(object) sys:py}.executable;
47+
return {sys:py}.executable;
4748
""");
4849

4950
Console.WriteLine(result);
@@ -60,20 +61,46 @@ private static async Task Sample()
6061
var asyncIO = PythonExtensions.GetConcretePythonModule<AsyncIOModule>();
6162

6263
const int DELAY_SECONDS = 3;
63-
64-
var asyncCoroutine = RawPython.Run<dynamic>(
64+
65+
var asyncCoroutine = RawPython.Run<dynamic>
66+
(
6567
$"""
66-
async def async_coroutine():
67-
print("{nameof(asyncIO)} is running!");
68-
await {(object) asyncIO.Sleep(DELAY_SECONDS):py};
69-
return async_coroutine();
70-
""");
68+
async def async_coroutine():
69+
print("{nameof(asyncIO)} is running!");
70+
await {asyncIO.Sleep(DELAY_SECONDS):py};
71+
return async_coroutine();
72+
"""
73+
);
7174

7275
var task = asyncIO.RunCoroutine(asyncCoroutine, handle);
7376

7477
await task;
7578

7679
Console.WriteLine($"Hello after {DELAY_SECONDS} seconds");
80+
81+
var threadedTask = Task.Run(() =>
82+
{
83+
using (new PythonHandle())
84+
{
85+
Console.WriteLine("Threaded task running when long-running C# code is");
86+
}
87+
});
88+
89+
LongRunningCSharpCode();
90+
91+
return;
92+
93+
void LongRunningCSharpCode()
94+
{
95+
using (handle.GetLongRunningCSharpRegion())
96+
{
97+
Console.WriteLine("Start of long-running C# code");
98+
99+
Thread.Sleep(1000);
100+
101+
Console.WriteLine("End of long-running C# code");
102+
}
103+
}
77104
}
78105
}
79106
}

0 commit comments

Comments
 (0)