টিএল; ডিআর এটি তুচ্ছ নয়
দেখে মনে হচ্ছে যে কোনও স্ট্রিমের বাফারগুলি পড়ে এমন কোনও স্ট্রাক্টের জন্য ইতিমধ্যে সম্পূর্ণ কোড পোস্ট করেছেUtf8JsonStreamReader
এবং এটি একটি ইউটিএফ 8 জসনআরডারকে ফিড করে, এর সাথে সহজেই বিশৃঙ্খলা মঞ্জুর করে JsonSerializer.Deserialize<T>(ref newJsonReader, options);
। কোডটিও তুচ্ছ নয়। সম্পর্কিত প্রশ্ন এখানে এবং উত্তর এখানে ।
যদিও এটি যথেষ্ট নয় - HttpClient.GetAsync
সম্পূর্ণ প্রতিক্রিয়া পাওয়ার পরে কেবল ফিরে আসবে, মূলত মেমরিতে সমস্ত কিছু বফার করে।
এটি এড়াতে, HTTPClient.GetAsync (স্ট্রিং, এইচটিটিপি কমপ্লায়শন অপশন) ব্যবহার করা উচিত HttpCompletionOption.ResponseHeadersRead
।
ডিসিরিয়ালাইজেশন লুপটি বাতিলকরণ টোকেনটিও পরীক্ষা করে দেখতে হবে এবং প্রস্থান করে বা এটি সিগন্যালড থাকলে নিক্ষেপ করা উচিত। অন্যথায় পুরো স্ট্রিমটি গ্রহণ এবং প্রক্রিয়াজাত না হওয়া পর্যন্ত লুপটি চলতে থাকবে।
এই কোডটি সম্পর্কিত উত্তরের উদাহরণে ভিত্তি HttpCompletionOption.ResponseHeadersRead
করে বাতিলকরণ টোকেন ব্যবহার করে এবং চেক করে। এটি JSON স্ট্রিংগুলিকে পার্স করতে পারে যাতে আইটেমগুলির যথাযথ অ্যারে থাকে eg
[{"prop1":123},{"prop1":234}]
প্রথম কলটি jsonStreamReader.Read()
অ্যারের শুরুতে চলে আসে যখন দ্বিতীয়টি প্রথম অবজেক্টের শুরুতে চলে যায়। অ্যারে ( ]
) এর শেষটি সনাক্ত হলে লুপটি নিজেই সমাপ্ত হয়।
private async IAsyncEnumerable<T> GetList<T>(Uri url, CancellationToken cancellationToken = default)
{
//Don't cache the entire response
using var httpResponse = await httpClient.GetAsync(url,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken);
using var stream = await httpResponse.Content.ReadAsStreamAsync();
using var jsonStreamReader = new Utf8JsonStreamReader(stream, 32 * 1024);
jsonStreamReader.Read(); // move to array start
jsonStreamReader.Read(); // move to start of the object
while (jsonStreamReader.TokenType != JsonTokenType.EndArray)
{
//Gracefully return if cancellation is requested.
//Could be cancellationToken.ThrowIfCancellationRequested()
if(cancellationToken.IsCancellationRequested)
{
return;
}
// deserialize object
var obj = jsonStreamReader.Deserialize<T>();
yield return obj;
// JsonSerializer.Deserialize ends on last token of the object parsed,
// move to the first token of next object
jsonStreamReader.Read();
}
}
জেএসএন টুকরো, একে স্ট্রিমিং জেএসওন ওরফে ... *
পৃথক JSON অবজেক্টগুলিকে কোনও ফাইলে সংযুক্ত করার জন্য ইভেন্ট স্ট্রিমিং বা লগিংয়ের দৃশ্যে এটি বেশ সাধারণ, প্রতি লাইনে একটি উপাদান:
{"eventId":1}
{"eventId":2}
...
{"eventId":1234567}
এটি বৈধ JSON নথি নয় তবে পৃথক টুকরা বৈধ। এটিতে বড় ডেটা / চূড়ান্ত একযোগে পরিস্থিতিগুলির জন্য বিভিন্ন সুবিধা রয়েছে। একটি নতুন ইভেন্ট যুক্ত করার জন্য কেবলমাত্র ফাইলটিতে একটি নতুন লাইন যুক্ত করা দরকার, পুরো ফাইলটিকে পার্সিং এবং পুনর্নির্মাণ নয়। প্রক্রিয়াজাতকরণ , বিশেষত সমান্তরাল প্রক্রিয়াকরণ দুটি কারণে সহজ:
- পৃথক উপাদানগুলি একবারে একটি করে স্রোত থেকে এক লাইন পড়ার মাধ্যমে পুনরুদ্ধার করা যায়।
- ইনপুট ফাইলটি সহজেই পার্টিশন এবং লাইন সীমানা জুড়ে বিভক্ত করা যেতে পারে, প্রতিটি অংশকে একটি পৃথক কর্মী প্রক্রিয়াতে খাওয়ানো হয়, যেমন একটি হডোপ ক্লাস্টারে, বা কোনও অ্যাপ্লিকেশনটিতে কেবল আলাদা থ্রেড: বিভক্ত পয়েন্টগুলি গণনা করুন যেমন কর্মীর সংখ্যা দ্বারা দৈর্ঘ্যকে ভাগ করে , তারপরে প্রথম নিউলাইনটি সন্ধান করুন। এ পর্যন্ত সমস্ত কিছু আলাদা কর্মীকে খাওয়ান।
স্ট্রিমরিডার ব্যবহার করা
এটি করার জন্য বরাদ্দ-ওয়াইয়ের উপায় হ'ল একটি টেক্সটরিডার ব্যবহার করা, একবারে একটি লাইন পড়ুন এবং এটি জসনসিরাইজার দিয়ে পার্স করুন ese ডিজিটালাইজ করুন :
using var reader=new StreamReader(stream);
string line;
//ReadLineAsync() doesn't accept a CancellationToken
while((line=await reader.ReadLineAsync()) != null)
{
var item=JsonSerializer.Deserialize<T>(line);
yield return item;
if(cancellationToken.IsCancellationRequested)
{
return;
}
}
কোডের তুলনায় এটি অনেক সহজ যা একটি উপযুক্ত অ্যারের ডিজিটালাইজ করে। দুটি বিষয় আছে:
ReadLineAsync
বাতিলকরণ টোকেন গ্রহণ করে না
- প্রতিটি পুনরাবৃত্তি একটি নতুন স্ট্রিং বরাদ্দ করে, আমরা সিস্টেম.টেক্সট.জসন ব্যবহার করে যা এড়াতে চেয়েছিলাম তার মধ্যে একটি
এটি যথেষ্ট হতে পারে যদিওReadOnlySpan<Byte>
জসনসিরাইজার দ্বারা প্রয়োজনীয় বাফারগুলি উত্পাদন করার চেষ্টা করা হচ্ছে eseডিজারালাইজেশন তুচ্ছ নয়।
পাইপলাইনস এবং সিকোয়েন্সরিডার
বরাদ্দগুলি এড়াতে, আমাদের ReadOnlySpan<byte>
স্ট্রিম থেকে একটি নেওয়া দরকার । এটি করার জন্য System.IO. পাইপলাইন পাইপ এবং সিকোয়েন্সরিডার স্ট্রাক্ট ব্যবহার করা দরকার । স্টিভ গর্ডনের একটি ভূমিকা সিকোয়েন্সরেডার ব্যাখ্যা করে যে কীভাবে এই শ্রেণিটি ডিলিমিটারগুলি ব্যবহার করে কোনও স্ট্রিমের ডেটা পড়তে ব্যবহার করা যেতে পারে।
দুর্ভাগ্যক্রমে, SequenceReader
একটি রেফ স্ট্রাক্ট যার অর্থ এটি অ্যাসিঙ্ক বা স্থানীয় পদ্ধতিতে ব্যবহার করা যায় না। এজন্য স্টিভ গর্ডন তাঁর নিবন্ধে একটি তৈরি করেছেন
private static SequencePosition ReadItems(in ReadOnlySequence<byte> sequence, bool isCompleted)
আইটেমগুলি পড়ার পদ্ধতিটি একটি ReadOnlySequence গঠন করে এবং শেষের অবস্থানটি ফেরত দেয়, তাই পাইপ রিডার এটি থেকে পুনরায় শুরু করতে পারে। দুর্ভাগ্যবশত আমরা একটি IEnumerable বা IAsyncEnumerable আসতে করতে চান, এবং পুনরুক্তিকারীর পদ্ধতি পছন্দ না in
বা out
পারেন প্যারামিটার।
আমরা একটি তালিকা বা কাতারে ডিসরিয়ালাইজড আইটেমগুলি সংগ্রহ করতে এবং সেগুলিকে একক ফলাফল হিসাবে ফিরিয়ে দিতে পারি, তবে এটি এখনও তালিকা, বাফার বা নোড বরাদ্দ করে এবং ফিরে আসার আগে বাফারের সমস্ত আইটেমকে ডিসস্রায়াল করার জন্য অপেক্ষা করতে হয়েছিল:
private static (SequencePosition,List<T>) ReadItems(in ReadOnlySequence<byte> sequence, bool isCompleted)
আমাদের এমন কিছু দরকার যা একটি পুনরাবৃত্তকারী পদ্ধতির প্রয়োজন ছাড়াই একটি গণনার মতো কাজ করে, অ্যাসিঙ্কের সাথে কাজ করে এবং সমস্ত কিছু বাফার করে না।
একটি IAsyncEnumerable উত্পাদন করতে চ্যানেল যুক্ত করা
ChannelReader.ReadAllAsync একটি IAsyncEnumerable ফেরৎ। আমরা এমন পদ্ধতিগুলি থেকে চ্যানেল রিডার ফিরিয়ে দিতে পারি যা পুনরাবৃত্তকারী হিসাবে কাজ করতে পারে না এবং এখনও ক্যাশে ছাড়াই উপাদানগুলির একটি স্ট্রিম উত্পাদন করে।
চ্যানেলগুলি ব্যবহার করার জন্য স্টিভ গর্ডনের কোডটি মানিয়ে নেওয়া, আমরা রিড আইটেমগুলি (চ্যানেল লেখক ...) এবং ReadLastItem
পদ্ধতিগুলি পাই । প্রথম এক, একটি সময়ে একটি আইটেম সার্চ, একটি newline ব্যবহার পর্যন্ত ReadOnlySpan<byte> itemBytes
। এটি দ্বারা ব্যবহার করা যেতে পারে JsonSerializer.Deserialize
। যদি ReadItems
ডিলিমিটারটি খুঁজে না পায় তবে এটি তার অবস্থানটি ফিরিয়ে দেয় যাতে পাইপলাইনার্ডার পরবর্তী অংশটিকে প্রবাহ থেকে টানতে পারে।
যখন আমরা শেষ খণ্ডে পৌঁছায় এবং অন্য কোনও ডিলিমিটার নেই, রিডলাস্ট আইটেম` বাকি বাইটগুলি পড়ে সেগুলি ডিসরিয়ালাইজ করে।
কোডটি স্টিভ গর্ডনের মতো প্রায় একই রকম। কনসোলটিতে লেখার পরিবর্তে আমরা চ্যানেল রাইটারে লিখি।
private const byte NL=(byte)'\n';
private const int MaxStackLength = 128;
private static SequencePosition ReadItems<T>(ChannelWriter<T> writer, in ReadOnlySequence<byte> sequence,
bool isCompleted, CancellationToken token)
{
var reader = new SequenceReader<byte>(sequence);
while (!reader.End && !token.IsCancellationRequested) // loop until we've read the entire sequence
{
if (reader.TryReadTo(out ReadOnlySpan<byte> itemBytes, NL, advancePastDelimiter: true)) // we have an item to handle
{
var item=JsonSerializer.Deserialize<T>(itemBytes);
writer.TryWrite(item);
}
else if (isCompleted) // read last item which has no final delimiter
{
var item = ReadLastItem<T>(sequence.Slice(reader.Position));
writer.TryWrite(item);
reader.Advance(sequence.Length); // advance reader to the end
}
else // no more items in this sequence
{
break;
}
}
return reader.Position;
}
private static T ReadLastItem<T>(in ReadOnlySequence<byte> sequence)
{
var length = (int)sequence.Length;
if (length < MaxStackLength) // if the item is small enough we'll stack allocate the buffer
{
Span<byte> byteBuffer = stackalloc byte[length];
sequence.CopyTo(byteBuffer);
var item=JsonSerializer.Deserialize<T>(byteBuffer);
return item;
}
else // otherwise we'll rent an array to use as the buffer
{
var byteBuffer = ArrayPool<byte>.Shared.Rent(length);
try
{
sequence.CopyTo(byteBuffer);
var item=JsonSerializer.Deserialize<T>(byteBuffer);
return item;
}
finally
{
ArrayPool<byte>.Shared.Return(byteBuffer);
}
}
}
DeserializeToChannel<T>
পদ্ধতি, স্ট্রিম উপরে একটি পাইপলাইন পাঠক সৃষ্টি একটি চ্যানেল তৈরি করে এবং একজন শ্রমিক কাজের শুরু হয় যে পার্স খন্ডে তাদের push কর্মের চ্যানেলে:
ChannelReader<T> DeserializeToChannel<T>(Stream stream, CancellationToken token)
{
var pipeReader = PipeReader.Create(stream);
var channel=Channel.CreateUnbounded<T>();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
while (!token.IsCancellationRequested)
{
var result = await pipeReader.ReadAsync(token); // read from the pipe
var buffer = result.Buffer;
var position = ReadItems(writer,buffer, result.IsCompleted,token); // read complete items from the current buffer
if (result.IsCompleted)
break; // exit if we've read everything from the pipe
pipeReader.AdvanceTo(position, buffer.End); //advance our position in the pipe
}
pipeReader.Complete();
},token)
.ContinueWith(t=>{
pipeReader.Complete();
writer.TryComplete(t.Exception);
});
return channel.Reader;
}
ChannelReader.ReceiveAllAsync()
এর মাধ্যমে সমস্ত আইটেম গ্রাস করতে ব্যবহার করা যেতে পারে IAsyncEnumerable<T>
:
var reader=DeserializeToChannel<MyEvent>(stream,cts.Token);
await foreach(var item in reader.ReadAllAsync(cts.Token))
{
//Do something with it
}