টাস্ক। পরামিতি (গুলি) দিয়ে চলছে?


88

আমি একটি বহু-টাস্কিং নেটওয়ার্ক প্রকল্পে কাজ করছি এবং আমি নতুন Threading.Tasks। আমি একটি সাধারণ বাস্তবায়ন করেছি Task.Factory.StartNew()এবং আমি আশ্চর্য হয়েছি যে আমি এটি দিয়ে কীভাবে পারি Task.Run()?

এখানে মূল কোডটি রয়েছে:

Task.Factory.StartNew(new Action<object>(
(x) =>
{
    // Do something with 'x'
}), rawData);

আমি মধ্যে লাগছিল System.Threading.Tasks.Taskমধ্যে অবজেক্ট ব্রাউজার এবং আমি একটি খুঁজে পাইনি Action<T>মত প্যারামিটার। এখানে কেবলমাত্র প্যারামিটার Actionলাগে voidএবং কোনও প্রকার হয় না

এখানে কেবল দুটি জিনিসই একই রকম রয়েছে: static Task Run(Action action)এবং static Task Run(Func<Task> function)তবে উভয়ই পরামিতি পোস্ট করতে পারে না।

হ্যাঁ, আমি জানি আমি এটির জন্য একটি সহজ এক্সটেনশনটি পদ্ধতি তৈরি করতে পারেন কিন্তু আমার মূল প্রশ্ন আমরা একক লাইন তে এটি লিখতে পারেন হয় সঙ্গে Task.Run()?


আপনি প্যারামিটারের মানটি কী হতে চান তা পরিষ্কার নয় । এটা কোথা থেকে আসবে? যদি আপনি ইতিমধ্যে এটি পেয়েছেন, কেবল ল্যাম্বডা অভিব্যক্তিতে এটি ক্যাপচার করুন ...
জন স্কিটি

@ জোনস্কিট rawDataএকটি নেটওয়ার্ক ডেটা প্যাকেট যার একটি ধারক শ্রেণি রয়েছে (যেমন ডেটাপ্যাকেট) এবং আমি জিসির চাপ হ্রাস করতে এই দৃষ্টান্তটি পুনরায় ব্যবহার করছি। সুতরাং, আমি যদি rawDataসরাসরি Taskএটি ব্যবহার করি তবে এটি Taskপরিচালনা করার আগে এটি সম্ভবত (সম্ভবত) পরিবর্তিত হতে পারে । এখন, আমি মনে করি byte[]এটির জন্য আমি আরও একটি উদাহরণ তৈরি করতে পারি। আমি মনে করি এটি আমার পক্ষে সহজ সমাধান।
এমফতিহামার

হ্যাঁ, আপনার যদি বাইট অ্যারে ক্লোন করতে হয় তবে আপনি বাইট অ্যারের ক্লোন করে ফেলেন। একটি থাকার Action<byte[]>সাথে এটি পরিবর্তন হয় না।
জন স্কিটি

কোনও কার্যে পরামিতিগুলি পাস করার জন্য এখানে কয়েকটি ভাল সমাধান রয়েছে
জাস্ট শেডো

উত্তর:


116
private void RunAsync()
{
    string param = "Hi";
    Task.Run(() => MethodWithParameter(param));
}

private void MethodWithParameter(string param)
{
    //Do stuff
}

সম্পাদনা করুন

জনপ্রিয় চাহিদার কারণে আমি অবশ্যই লক্ষ করব যে Taskলঞ্চটি কলিং থ্রেডের সাথে সমান্তরালে চলবে। ডিফল্ট ধরে নিলে এটি । TaskSchedulerনেট ব্যবহার করবে ThreadPool। যাইহোক, এর অর্থ এই যে আপনি যে প্যারামিটারগুলিতে Taskএকসাথে একাধিক থ্রেড দ্বারা একসাথে অ্যাক্সেস করা হয়েছে সেগুলি ভাগ করে নেওয়ার স্থিতিতে পরিণত হওয়া উচিত। এর মধ্যে তাদের কলিং থ্রেডে অ্যাক্সেস অন্তর্ভুক্ত রয়েছে।

আমার উপরের কোডে সেই কেসটি সম্পূর্ণরূপে বানানো হয়েছে। স্ট্রিংগুলি অপরিবর্তনীয়। এজন্য আমি তাদের উদাহরণ হিসাবে ব্যবহার করেছি। তবে বলুন আপনি একটি ব্যবহার করছেন না String...

একটি সমাধান ব্যবহার asyncএবং await। এটি, ডিফল্টরূপে, SynchronizationContextকলিং থ্রেডটি ক্যাপচার করবে এবং কল করার পরে বাকী পদ্ধতিটির জন্য একটি ধারাবাহিকতা তৈরি করবে এবং এটি তৈরির awaitসাথে সংযুক্ত করবে Task। এই পদ্ধতিটি যদি উইনফোর্ডস জিইউআই থ্রেডে চলছে তবে তা টাইপ হবে WindowsFormsSynchronizationContext

ধারাবাহিকতা ক্যাপচারে ফিরে পোস্ট করার পরে চলবে SynchronizationContext- কেবলমাত্র ডিফল্টরূপে। সুতরাং আপনি awaitকল করার পরে আপনি যে থ্রেডটি শুরু করেছিলেন তাতে ফিরে আসবেন । আপনি এটি বিভিন্ন উপায়ে উল্লেখযোগ্যভাবে ব্যবহার করে পরিবর্তন করতে পারেন ConfigureAwait। সংক্ষেপে বলতে গেলে, যে পদ্ধতি বাকি পর্যন্ত করা হবে না পরেTask অন্য থ্রেডে সম্পন্ন করেছে। তবে কলিং থ্রেডটি কেবল সামান্য পদ্ধতিতে নয়, সমান্তরালে চলতে থাকবে।

বাকি পদ্ধতিটি সম্পূর্ণরূপে অপেক্ষা করার অপেক্ষায় আকাঙ্খিত হতে পারে বা নাও পারে। যদি সেই পদ্ধতিতে কোনও কিছু পরে না পৌঁছানো প্যারামিটারগুলি অ্যাক্সেস করে তবে Taskআপনি মোটেও ব্যবহার করতে চাইবেন না await

অথবা আপনি পদ্ধতিতে অনেক পরে এই পরামিতিগুলি ব্যবহার করেন। awaitঅবিলম্বে কোনও কারণ নেই যেহেতু আপনি নিরাপদে কাজ চালিয়ে যেতে পারেন। মনে রাখবেন, আপনি Taskফেরতটি একটি চলকতে এবং awaitএটি পরে জমা রাখতে পারেন - এমনকি একই পদ্ধতিতেও। উদাহরণস্বরূপ, একবার আপনাকে অন্য কিছু কাজ করার পরে নিরাপদে পাস করা প্যারামিটারগুলি অ্যাক্সেস করতে হবে। আবার, আপনি কি না প্রয়োজন awaitউপর Taskযখন আপনি এটি চালানো ঠিক আছে।

যাইহোক, পরামিতিগুলির সাথে সম্মত হয়ে এই থ্রেড-নিরাপদ করার সহজ উপায় Task.Runহ'ল এটি করা:

আপনিই প্রথম সাজাইয়া রাখা আবশ্যক RunAsyncসঙ্গে async:

private async void RunAsync()

গুরুত্বপূর্ণ তথ্য

সংযুক্ত ডকুমেন্টেশনের উল্লেখ অনুসারে চিহ্নিত পদ্ধতিটি বাতিল হওয়া উচিত নয় । এটির সাধারণ ব্যতিক্রম ইভেন্ট হ্যান্ডলারগুলি যেমন বাটন ক্লিক এবং এ জাতীয়। তারা অবশ্যই অকার্যকর ফিরে আসতে হবে। অন্যথায় আমি সর্বদা একটি বা ব্যবহার করার সময় ফেরত দেওয়ার চেষ্টা করি । এটি বেশ কয়েকটি কারণে ভাল অনুশীলন।async TaskTask<TResult>async

এখন আপনি নীচের মত awaitচলতে পারেন Task। আপনি awaitছাড়া ব্যবহার করতে পারবেন না async

await Task.Run(() => MethodWithParameter(param));
//Code here and below in the same method will not run until AFTER the above task has completed in one fashion or another

সুতরাং, সাধারণভাবে, যদি আপনি awaitএই কাজটি করে থাকেন তবে আপনি এক সাথে একাধিক থ্রেড থেকে কোনও কিছুতে পরিবর্তন আনার সমস্ত ক্ষতিসাধনগুলি একটি সম্ভাব্য ভাগ করা সংস্থান হিসাবে পরামিতিগুলিতে পাস করা চিকিত্সা এড়াতে পারবেন। এছাড়াও, ক্লোজার থেকে সাবধান থাকুন । আমি তাদের গভীরতার সাথে আবরণ করব না তবে লিঙ্কযুক্ত নিবন্ধটি এটির দুর্দান্ত কাজ করে does

সাইড নোট

কিছুটা সামান্য বিষয়, তবে উইনফোর্ডস জিইউআই থ্রেডটি চিহ্নিত হওয়ার কারণে এটি যে কোনও ধরণের "ব্লকিং" ব্যবহার করে সাবধান হন [STAThread]। ব্যবহার awaitকরা মোটেই অবরুদ্ধ হবে না, তবে আমি মাঝে মাঝে এটি কোনও ধরণের ব্লকিংয়ের সাথে ব্যবহার করে দেখি।

"ব্লক" উদ্ধৃতিতে রয়েছে কারণ আপনি প্রযুক্তিগতভাবে উইনফর্মস জিইউআই থ্রেডটি ব্লক করতে পারবেন না । হ্যাঁ, আপনি যদি lockউইনফোর্ডস জিইউআই থ্রেড ব্যবহার করেন তবে এটি "অবরুদ্ধ" বলে ভেবেও বার্তা পাম্প করবে । এটা না।

এটি খুব বিরল ক্ষেত্রে উদ্ভট বিষয়গুলির কারণ হতে পারে। lockউদাহরণস্বরূপ, পেইন্টিংয়ের সময় আপনি কখনই ব্যবহার করতে চান না তার একটি কারণ। তবে এটি একটি সীমাবদ্ধ এবং জটিল কেস; তবে আমি এটি পাগল সমস্যার কারণ দেখেছি। সুতরাং আমি সম্পূর্ণতার জন্য এটি নোট করেছি।


23
আপনি অপেক্ষা করছেন না Task.Run(() => MethodWithParameter(param));। যার অর্থ হ'ল যদি এর পরেparam সংশোধন করা হয় তবে Task.Runআপনার উপর অপ্রত্যাশিত ফলাফল থাকতে পারে MethodWithParameter
আলেকজান্দে সেভেরিনো

8
এটি ভুল হলে কেন এটি একটি গৃহীত উত্তর। এটি রাষ্ট্রের বস্তুটি পাস করার মতো নয়।
ডিম পাভলিখিন

7
@ Zer0 একটি রাষ্ট্র বস্তুর Task.Factory.StartNew দ্বিতীয় paremeter হয় msdn.microsoft.com/en-us/library/dd321456(v=vs.110).aspx এবং এটি মুহূর্তে বস্তুর মান সংরক্ষণ করে স্টার্টনিউতে কল করুন, যখন আপনার উত্তরটি একটি ক্লোজার তৈরি করে, যা রেফারেন্স রাখে (যদি টাস্ক চালুর আগে প্যারামের মান পরিবর্তন হয় তবে এটিও কার্যক্রমে পরিবর্তিত হবে), সুতরাং আপনার কোডটি প্রশ্নটি যা বলছে তার সমতুল্য নয় । উত্তরটি হ'ল সত্য যে এটি টাস্ক.রুন () দিয়ে লেখার কোনও উপায় নেই।
ডিম পাভলিখিন

4
@ জের0 সম্ভবত আপনার উত্স কোডটি পড়া উচিত। একজন রাষ্ট্রীয় বস্তুটি পাস করে, অন্যটি দেয় না। ভিক্ষা থেকেই যা বলেছি। Task.Run হয় না Task.Factory.StartNew জন্য একটি সংক্ষিপ্ত সরাসরি। উত্তরাধিকারগত কারণে রাষ্ট্রীয় অবজেক্ট সংস্করণটি সেখানে রয়েছে, তবে এটি এখনও রয়েছে এবং এটি কখনও কখনও ভিন্নরকম আচরণ করে, তাই লোকেদের সে সম্পর্কে সচেতন হওয়া উচিত।
ডিম পাভলিখিন

4
টুবের নিবন্ধটি পড়তে আমি এই বাক্যটি হাইলাইট করব "আপনি ওভারলোডগুলি ব্যবহার করতে পারেন যা অবজেক্টের অবস্থা গ্রহণ করে, যা কার্য সম্পাদন-সংবেদনশীল কোড পাথের জন্য ক্লোজার এবং সংশ্লিষ্ট বরাদ্দগুলি এড়াতে ব্যবহার করা যেতে পারে"। আমি মনে করি টাস্ক বিবেচনা করার সময় @ জিরো এটিই বোঝাচ্ছে Start স্টার্টনিউ ব্যবহারের জন্য চালান।
ডেভিডকারার

34

প্যারামিটারগুলিতে "পাস ইন" করতে ভেরিয়েবল ক্যাপচার ব্যবহার করুন।

var x = rawData;
Task.Run(() =>
{
    // Do something with 'x'
});

আপনি rawDataসরাসরি ব্যবহার করতে পারেন তবে আপনাকে অবশ্যই সতর্কতা অবলম্বন করতে হবে, যদি আপনি rawDataকোনও কাজের বাইরের মান পরিবর্তন করেন (উদাহরণস্বরূপ একটি forলুপের মধ্যে একটি পুনরুক্তি ) এটিও কার্যের অভ্যন্তরের মানকে পরিবর্তন করতে পারে।


11
কল করার পরে ডায়াল ভেরিয়েবল পরিবর্তন হতে পারে এই গুরুত্বপূর্ণ সত্যতা বিবেচনার জন্য +1 Task.Run
আলেকজান্দ্রে সেভেরিনো

4
এটা কিভাবে সাহায্য করবে? আপনি যদি টাস্ক থ্রেডের ভিতরে এক্স ব্যবহার করেন, এবং এক্স কোনও জিনিসের একটি রেফারেন্স, এবং যদি টাস্ক থ্রেড চলমান অবস্থায় একই সময়ে যদি বস্তুটি সংশোধন করা হয় তবে তা ধ্বংসাত্মক হতে পারে।
ওভি

4
@ ওভি-ওয়ানকেনোবি হ্যাঁ, তবে এই প্রশ্নটি যে ছিল তা নয়। এটি কীভাবে প্যারামিটারটি পাস করবে। আপনি যদি কোনও সাধারণ ক্রিয়ায় প্যারামিটার হিসাবে কোনও সামগ্রীর রেফারেন্সটি পাস করেন তবে সেখানেও ঠিক একই সমস্যা হবে have
স্কট চেম্বারলাইন

হ্যাঁ এটি কাজ করে না। কলিং থ্রেডে আমার টাস্কের এক্স এর কোনও রেফারেন্স নেই। আমি শুধু নাল পেতে।
ডেভিড দাম

স্কট চেম্বারলাইন, ক্যাপচারের মাধ্যমে যুক্তিটি নিজের ইস্যু নিয়ে আসে। বিশেষত, স্মৃতিশক্তি ফাঁস এবং মেমরি চাপের বিষয়টি রয়েছে। বিশেষত যখন আপনি স্কেল করার চেষ্টা করেন। (আরও তথ্যের জন্য "8 টি উপায় যা আপনি মেমোরি ফাঁস করতে পারেন" দেখুন)।
রাশাদিভেরা

7

এখন থেকে আপনি এগুলিও করতে পারেন:

Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)

এটি সর্বোত্তম উত্তর কারণ এটি কোনও রাজ্যকে প্রবেশের অনুমতি দেয় এবং কাদেন বার্গার্টের উত্তরে উল্লিখিত সম্ভাব্য পরিস্থিতি রোধ করে । উদাহরণস্বরূপ, যদি আপনাকে IDisposableপুনঃশিকার সতর্কতা "ক্যাপচারড ভেরিয়েবলটি বাইরের স্কোপগুলিতে নিষ্পত্তি করা হয়" সমাধান করার জন্য টাস্ক ডেলিগেটের কাছে কোনও বস্তু পাস করার প্রয়োজন হয় , এটি খুব সুন্দর করে এটি করে। জনপ্রিয় বিশ্বাসের বিপরীতে Task.Factory.StartNew, Task.Runযেখানে আপনাকে রাজ্যটি পাস করতে হবে তার পরিবর্তে ব্যবহার করার ক্ষেত্রে কোনও অসুবিধা নেই । এখানে দেখুন ।
নিও

7

আমি জানি এটি একটি পুরানো থ্রেড, তবে গ্রহণযোগ্য পোস্টটিতে এখনও একটি সমস্যা আছে বলে আমি শেষ পর্যন্ত একটি সমাধান ভাগ করতে চেয়েছিলাম।

সমস্যাটি:

আলেকজান্দ্রে সেভেরিনো দ্বারা নির্দেশিত হিসাবে, যদি param(নীচের ফাংশনে) ফাংশন কলের কিছুক্ষণ পরে পরিবর্তন হয় তবে আপনি কিছুটা অপ্রত্যাশিত আচরণ পেতে পারেন MethodWithParameter

Task.Run(() => MethodWithParameter(param)); 

আমার সমাধান:

এটির জন্য অ্যাকাউন্ট করতে আমি নীচের কোডের লাইনের মতো আরও কিছু লেখা শেষ করেছি:

(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);

টাস্কটি শুরু করার পরে প্যারামিটারটি খুব দ্রুত পরিবর্তিত হয়েছিল (যেটি পোস্ট সলিউশন নিয়ে সমস্যা সৃষ্টি করেছিল) সত্ত্বেও এটি নিরাপদে প্যারামিটারটি নিরাপদভাবে অ্যাসিঙ্ক্রোনসভাবে ব্যবহার করার অনুমতি দিয়েছে।

এই পদ্ধতির ব্যবহার করে param(মানের ধরণ) এর মানটি পাস হয়ে যায়, সুতরাং যদি অ্যাসিঙ্ক পদ্ধতিটি paramপরিবর্তনের পরেও চালিত হয় তবে কোডটির এই লাইনটি চলার সময় pযা কিছু মান paramছিল তা হবে ।


4
আমি অধীর আগ্রহে অপেক্ষা করি যারাই কম ওভারহেড দিয়ে আরও সুস্পষ্টভাবে এটি করার কোনও উপায় সম্পর্কে ভাবতে পারে। এটি স্বভাবতই কুরুচিপূর্ণ।
কাদেন বার্গার্ট

4
আপনি এখানে যান:var localParam = param; await Task.Run(() => MethodWithParam(localParam));
স্টিফেন ক্লিয়ারি

4
যা, যাইহোক, দেড় বছর আগে স্টিফেন তার উত্তরে ইতিমধ্যে আলোচনা করেছেন।

4
@ সার্ভি: আসলে স্কট এর উত্তর ছিল । আমি এটার জবাব দিলাম না।
স্টিফেন ক্লিয়ারি

স্কটের উত্তরটি আমার পক্ষে আসলে কাজ করবে না, কারণ আমি এটি লুপের জন্য চালাচ্ছিলাম। স্থানীয় পরমটি পরবর্তী পুনরাবৃত্তিতে পুনরায় সেট করা হত। আমি পোস্ট করা উত্তরের পার্থক্যটি হ'ল পরম ল্যাম্বডা অভিব্যক্তির স্কোপটিতে অনুলিপি করা হয়েছে, সুতরাং ভেরিয়েবলটি তাত্ক্ষণিকভাবে নিরাপদ। স্কটের উত্তরে, প্যারামিটারটি এখনও একই সুযোগে রয়েছে, সুতরাং এটি লাইনটি কল করতে এবং অ্যাসিঙ্ক ফাংশন সম্পাদন করার মধ্যে এখনও পরিবর্তন হতে পারে।
কাদেন বার্গার্ট

5

শুধু টাস্ক.রুন ব্যবহার করুন

var task = Task.Run(() =>
{
    //this will already share scope with rawData, no need to use a placeholder
});

অথবা, যদি আপনি এটি কোনও পদ্ধতিতে ব্যবহার করতে চান এবং পরে কার্যটির জন্য অপেক্ষা করেন

public Task<T> SomethingAsync<T>()
{
    var task = Task.Run(() =>
    {
        //presumably do something which takes a few ms here
        //this will share scope with any passed parameters in the method
        return default(T);
    });

    return task;
}

4
ক্লোজারগুলি সম্পর্কে সতর্কতা অবলম্বন করুন আপনি for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }যদি rawDataসেভাবে করেন তবে ওপির স্টার্টনিউ উদাহরণ হিসাবে যেমন পাস করা হয়েছে তেমন আচরণ করবে না ।
স্কট চেম্বারলাইন

@ স্কটচ্যাম্বারলাইন - এটি অন্যরকম উদাহরণ বলে মনে হচ্ছে;) আমি আশা করব যে বেশিরভাগ লোক ল্যাম্বডা মানগুলি বন্ধ করার বিষয়ে বুঝতে পারে।
ট্র্যাভিস জে

4
এবং যদি পূর্ববর্তী মন্তব্যগুলি কোনও অর্থবহ না করে থাকে তবে দয়া করে বিষয়টিতে এরিক লিপারের ব্লগটি দেখুন: ব্লগস.এমএসএনএন / বি / এরিক্লিপার্ট / অর্চিভ / ২০০৯ / 11 / 12/… কেন এটি খুব ভাল হয় তা ব্যাখ্যা করে।
ট্র্যাভিস জে

2

এটি অস্পষ্ট নয় যে মূল সমস্যাটি আমার একই সমস্যা ছিল: পুনরুক্তির মান সংরক্ষণ করার সময় কোনও লুপের অভ্যন্তরে গণনাতে সর্বাধিক সিপিইউ থ্রেড অর্জন করা এবং একটি কর্মী ফাংশনে একটি টন ভেরিয়েবল এড়াতে ইনলাইন রাখা।

for (int i = 0; i < 300; i++)
{
    Task.Run(() => {
        var x = ComputeStuff(datavector, i); // value of i was incorrect
        var y = ComputeMoreStuff(x);
        // ...
    });
}

আমি বাইরের পুনরুক্তি পরিবর্তন করে এবং গেটের সাথে এর মান স্থানীয়করণ করে কাজ করতে পেরেছি।

for (int ii = 0; ii < 300; ii++)
{
    System.Threading.CountdownEvent handoff = new System.Threading.CountdownEvent(1);
    Task.Run(() => {
        int i = ii;
        handoff.Signal();

        var x = ComputeStuff(datavector, i);
        var y = ComputeMoreStuff(x);
        // ...

    });
    handoff.Wait();
}

0

আইডিয়া হ'ল উপরের মতো সিগন্যাল ব্যবহার করা এড়ানো। কোন মানকে স্ট্রাক্টে পাম্প করা সেই মানগুলিকে পরিবর্তন করতে বাধা দেয় (স্ট্রাক্টে)। আমার নিম্নোক্ত সমস্যাটি ছিল: ডুপসামিং (আই) বলার আগে লুপ ভার আমি বদলে যাব (আমি লুপের শেষে বৃদ্ধি পেয়েছিলাম () => ডসোমিংথিং (আই, আই) বলা হয়েছিল)। স্ট্রাইকগুলির সাথে এটি আর ঘটে না। অদ্ভুত বাগটি খুঁজে পেতে: ডসোমিংথিং (i, ii ) দুর্দান্ত দেখায় তবে কখনই নিশ্চিত হয় না যে এটি প্রতিবারই আই এর জন্য আলাদা মান সহ (বা i = 100 দিয়ে মাত্র 100 বার) আসে কিনা, তাই -> স্ট্রাক্ট

struct Job { public int P1; public int P2; }
…
for (int i = 0; i < 100; i++) {
    var job = new Job { P1 = i, P2 = i * i}; // structs immutable...
    Task.Run(() => DoSomething(job));
}

4
যদিও এটি প্রশ্নের উত্তর দিতে পারে, তা পর্যালোচনার জন্য পতাকাঙ্কিত করা হয়েছিল। কোনও ব্যাখ্যা সহ উত্তরগুলি প্রায়শই নিম্ন-মানের হিসাবে বিবেচিত হয়। কেন এটি সঠিক উত্তর তা সম্পর্কে দয়া করে কিছু ভাষ্য সরবরাহ করুন।
ড্যান
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.