We're having to call data from a hideously limited Web API provided to us by means beyond our control. The logic flow is as follows:
For each product represented by a list of ID values
Get each batch of sub-categories of type FOO (100 records max. per call)
Keep calling the above until no records remain
Get each batch of sub-categories of type BAR (100 records max. per call)
Keep calling the above until no records remain
This at present is generating nearly 100 web API calls (we've asked the provider, and there's no improving the web API to mitigate this).
I'm worried that performance will suffer drastically because of this, so am trying to get my head around the asynchronous alternative, in the hope that this will help. One major issue is that the data can only be called once. After that, it becomes locked and isn't resent, which is a major limitation to our testing.
I've read here, here and here, but am struggling to adapt to my code as I think I need two await calls rather than one, and am worried things will get messed up.
Can anyone please apply the await and async logic into this pseudo code so that I can then read up and try follow the flow of what's happening?
public class DefaultController : Controller
{
public ActionResult Index()
{
var idlist = new List<String>() {"123", "massive list of strings....", "789"};
var xdoc = new XDocument();
xdoc.Declaration = new XDeclaration("1.0", Encoding.Unicode.WebName, "yes");
var xroot = new XElement("records");
xdoc.Add(xroot);
foreach (string id in idlist)
{
// Get types FOO -----------------------------------
Boolean keepGoingFOO = true;
while (keepGoingFOO)
{
// 100 records max per call
var w = new WebServiceClient();
request.enumType = enumType.FOO;
var response = w.response();
foreach (ResultItem cr in response.ResultList)
{
var xe = new XElement("r");
// create XML
xroot.Add(xe);
}
keepGoingFOO = response.moreRecordsExist;
}
// Get types BAR -----------------------------------
Boolean keepGoingBAR = true;
while (keepGoingBAR)
{
// 100 records max per call
var w = new WebServiceClient();
request.enumType = enumType.BAR;
var response = w.response();
foreach (ResultItem cr in response.ResultList)
{
var xe = new XElement("r");
// create XML
xroot.Add(xe);
}
keepGoingBAR = response.moreRecordsExist;
}
}
return View(xdoc);
}
}
Should get you started:
public async ActionResult Index()
{
var idlist = new List<string>() { "123", "massive list of strings....", "789" };
IEnumerable<XElement> list = await ProcessList(idlist);
//sort the list as it will be completely out of order
return View(xdoc);
}
public async Task<IEnumerable<XElement>> ProcessList(IEnumerable<string> idlist)
{
IEnumerable<XElement>[] processList = await Task.WhenAll(idlist.Select(FooBar));
return processList.Select(x => x.ToList()).SelectMany(x => x);
}
private async Task<IEnumerable<XElement>> FooBar(string id)
{
Task<IEnumerable<XElement>> foo = Foo(id);
Task<IEnumerable<XElement>> bar = Bar(id);
return ((await bar).Concat(await foo));
}
private async Task<IEnumerable<XElement>> Bar(string id)
{
var localListOfElements = new List<XElement>();
var keepGoingFoo = true;
while (keepGoingFoo)
{
var response = await ServiceCallAsync(); //make sure you use the async version
localListOfElements.Add(new XElement("r"));
keepGoingFoo = response.moreRecordsExist;
}
return localListOfElements;
}
private async Task<IEnumerable<XElement>> Foo(string id)
{
var localListOfElements = new List<XElement>();
var keepGoingFoo = true;
while (keepGoingFoo)
{
var response = await ServiceCallAsync(); //make sure you use the async version
localListOfElements.Add(new XElement("r"));
keepGoingFoo = response.moreRecordsExist;
}
return localListOfElements;
}
private async Task<Response> ServiceCallAsync()
{
await Task.Delay(1000);//simulation
return new Response();
}
There are multiple issues with your code, it can be greatly improved by refactoring as the only items changing in between for loops is the request.EnumType. And performance could be greatly improved with proper use of async awaits - as long as the ids are independent the question is not parallelizing two - but rather parallelizing as much as possible.
The portion slowing your time is not xml access - it is web api calls.
I would refactor it as
async Task<Tuple<string, enumType, XElement>> SendRequest(string id, enumType input){
..
}
And replace the for loop with
List<Tuple<string, enumType>> tupleList = idList.Select(id => Tuple.Create(id, enumType.BAR)).ToList();
tupleList.Concat(idList.Select(id => Tuple.Create(id, enumType.FOO)).ToList());
Task<Tuple<string, enumType, XElement>>[] all = tupleList
.Select(c => SendRequest(c.Item1, c.Item2))
.ToArray();
var res = await Task.WhenAll(tasks);
The res variable will contain all the XElement values you want to add which should be fast. You can instead use a Key Value pair with Tuple of id-enumType being the key as well yet the idea is the same.
To make the solution more elegant, I would hide the batching behind an enumerable object, like weston proposed, but instead of putting all items on one list - consume them as soon as they are available (to minimize memory utilization).
With AsyncEnumerator NuGet Package you can write the code like this:
public class DefaultController : Controller
{
public async ActionResult Index()
{
var idlist = new List<String>() { "123", "massive list of strings....", "789" };
var xdoc = new XDocument();
xdoc.Declaration = new XDeclaration("1.0", Encoding.Unicode.WebName, "yes");
var xroot = new XElement("records");
xdoc.Add(xroot);
foreach (string id in idlist) {
// Get types FOO -----------------------------------
var foos = EnumerateItems(enumType.FOO);
await foos.ForEachAsync(cr => {
var xe = new XElement("r");
// create XML
xroot.Add(xe);
});
// Get types BAR -----------------------------------
var bars = EnumerateItems(enumType.BAR);
await foos.ForEachAsync(cr => {
var xe = new XElement("r");
// create XML
xroot.Add(xe);
});
}
return View(xdoc);
}
public IAsyncEnumerable<ResultItem> EnumerateItems(enumType itemType)
{
return new AsyncEnumerable<ResultItem>(async yield => {
Boolean keepGoing = true;
while (keepGoing) {
// 100 records max per call
var w = new WebServiceClient();
request.enumType = itemType;
// MUST BE ASYNC CALL
var response = await w.responseAsync();
foreach (ResultItem cr in response.ResultList)
await yield.ReturnAsync(cr);
keepGoing = response.moreRecordsExist;
}
});
}
}
Note 1: by making everything async actually does not improve the performance of one single routine, and in fact makes it little bit slower (due to the overhead of async state machines and TPL tasks). However, it helps to better utilize the worker threads in your app overall.
Note 2: your client request must be Async, otherwise the optimization does not make sense - if you do it synchronously it blocks the thread and just waits when the server respond.
Note 3: please pass a CancellationToken to every Async method - that's a good practice.
Note 4: judging from the code, you have a web server, so I do not recommend to run all batches for all items in parallel and do Task.WhenAll to wait for them - if your service client is synchronous (the w.response call) then it will just block a lot of threads and your whole web service might get unresponsive.
In the proposed solution you can do one trick to do thing in parallel - your can read ahead the next batch while processing the current one like this:
public IAsyncEnumerable<ResultItem> EnumerateItemsWithReadAhead(enumType itemType)
{
return new AsyncEnumerable<ResultItem>(async yield => {
Task<Response> nextBatchTask = FetchNextBatch(itemType);
Boolean keepGoing = true;
while (keepGoing) {
var response = await nextBatchTask;
// Kick off the next batch request (read ahead)
keepGoing = response.moreRecordsExist;
if (keepGoing)
nextBatchTask = FetchNextBatch(itemType);
foreach (ResultItem cr in response.ResultList)
await yield.ReturnAsync(cr);
}
});
}
private Task<Response> FetchNextBatch(enumType itemType)
{
// 100 records max per call
var w = new WebServiceClient();
request.enumType = itemType;
// MUST BE ASYNC
return w.responseAsync();
}
Related
I have a chron job which calls a database table and gets about half a million records returned. I need to loop through all of that data, and send API post's to a third party API. In general, this works fine, but the processing time is forever (10 hours). I need a way to speed it up. I've been trying to use a list of Task with SemaphoreSlim, but running into issues (it doesn't like that my api call returns a Task). I'm wondering if anyone has a solution to this that won't destroy the VM's memory?
Current code looks something like:
foreach(var data in dataList)
{
try
{
var response = await _apiService.PostData(data);
_logger.Trace(response.Message);
} catch//
}
But I'm trying to do this and getting the syntax wrong:
var tasks = new List<Task<DataObj>>();
var throttler = new SemaphoreSlim(10);
foreach(var data in dataList)
{
await throttler.WaitAsync();
tasks.Add(Task.Run(async () => {
try
{
var response = await _apiService.PostData(data);
_logger.Trace(response.Message);
}
finally
{
throttler.Release();
}
}));
}
Your list is of type Task<DataObj>, but your async lambda doesn't return anything, so its return type is Task. To fix the syntax, just return the value:
var response = await _apiService.PostData(data);
_logger.Trace(response.Message);
return response;
As others have noted in the comments, I also recommend not using Task.Run here. A local async method would work fine:
var tasks = new List<Task<DataObj>>();
var throttler = new SemaphoreSlim(10);
foreach(var data in dataList)
{
tasks.Add(ThrottledPostData(data));
}
var results = await Task.WhenAll(tasks);
async Task<DataObj> ThrottledPostData(Data data)
{
await throttler.WaitAsync();
try
{
var response = await _apiService.PostData(data);
_logger.Trace(response.Message);
return response;
}
finally
{
throttler.Release();
}
}
I have a Task that generates a PDF file for an order (it takes about 10 seconds to create one PDF):
public async Task GeneratePDF(Guid Id) {
var order = await
_context
.Orders
.Include(order => order.Customer)
... //a lot more Include and ThenInclude statements
.FirstOrDefaultAsync(order ==> order.Id == Id);
var document = ... //PDF generated here, takes about 10 seconds
order.PDF = document ;
_context.SaveChangesAsync();
}
I tried the following:
public async Task GenerateAllPDFs() {
var orderIds = await _context.Orders.Select(order=> order.Id).ToListAsync();
foreach (var id in orderIds)
{
_ = GeneratePDF(id).ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
}
}
this gives me the error:
System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
If I change the task as follows...
public async Task GenerateAllPDFs() {
var orderIds = await _context.Orders.Select(order=> order.Id).ToListAsync();
foreach (var id in orderIds)
{
_ = await GeneratePDF(id);
}
}
...it runs the task for each order in series, taking ages to complete (I have a few thousands orders, taking about 10 seconds per order)...
How can I run this task in parallel for all orders in the context, so that the time it takes to complete is much less than sequential processing?
You can map your order IDs to tasks and await them all like:
public async Task GeneratePDF(Order order) {
var document = ... //PDF generated here, takes about 10 seconds
order.PDF = document ;
}
public async Task GenerateAllPDFs() {
var orderIds = await _context.Orders.ToListAsync();
var tasks = orderIds.Select((order) => GeneratePDF(order).ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted));
await Task.WhenAll(tasks);
await _context.SaveChangesAsync();
}
Here is my suggestion from the comment as an answer. I would split it in 3 parts:
1) get all orders,
2) then do a Parallel.Foreach to generate all documents in parallel. and assign each document to the proper order and in the end
3) do a single _context.SaveChangesAsync(); to make a bulk update on the data on the server
public async Task GenerateAllPDFs()
{
var allOrders = await _context.Orders.ToListAsync();
System.Threading.Tasks.Parallel.ForEach(allOrders, order =>
{
var document = ... //PDF generated here, takes about 10 seconds
order.PDF = document ;
});
await _context.SaveChangesAsync();
}
You need to implement parallel programing.
https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming
public class Example
{
public static void Main()
{
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++) {
taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
CustomData data = obj as CustomData;
if (data == null)
return;
data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
},
new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
}
Task.WaitAll(taskArray);
foreach (var task in taskArray) {
var data = task.AsyncState as CustomData;
if (data != null)
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum);
}
}
}
I think I will have to "duplicate" the GeneratePDF method to facilitate the batch processing by implementing the other answers , since I need this method also in non-batch mode...
I have this test method
[Fact]
public async Task MethodName(){
var strings = new BufferBlock<string>();
var ints = new BufferBlock<int>();
ConcurrentStack<Tuple<string,int> > concurrentStack=new ConcurrentStack<Tuple<string, int>>();
var transformBlock = new TransformBlock<Tuple<string,int>,string>(async tuple => {
var formattableString = $"{tuple.Item2}-{tuple.Item1}-{Environment.CurrentManagedThreadId}";
if (concurrentStack.TryPeek(out var result) && Equals(result, tuple)){
WriteLine($"Await -> {formattableString}");
await Task.Delay(1000);
}
concurrentStack.Push(tuple);
await DoAsync(tuple);
WriteLine(formattableString);
return formattableString;
});
var joinBlock = new JoinBlock<string,int>();
var dataflowLinkOptions = new DataflowLinkOptions(){PropagateCompletion = true};
joinBlock.LinkTo(transformBlock,dataflowLinkOptions);
strings.LinkTo(joinBlock.Target1,dataflowLinkOptions);
ints.LinkTo(joinBlock.Target2,dataflowLinkOptions);
strings.Post("a");
strings.Post("a");
strings.Post("b");
strings.Post("b");
ints.Post(1);
ints.Post(1);
ints.Post(2);
ints.Post(1);
strings.Complete();
transformBlock.LinkTo(DataflowBlock.NullTarget<string>());
await transformBlock.Completion;
}
which outputs
15:36:53.2369|1-a-28
15:36:53.2369|Await -> 1-a-28
15:36:54.2479|1-a-28
15:36:54.2479|2-b-38
15:36:54.2479|1-b-38
I am looking to throttle this operation in a way that if the previous item is the same as the current to wait for a certain amount of time. I would like to use this flow in a parallel scenario and my guess is that RX extensions could be the solution here as I do not feel my approach is correct although it seems to produce the correct results.
UPDATE - CONCRETE EXAMPLE
In comments #Enigmativity requested to provide examples of my sources and transforms, so I think the real implementation is the best fit to this.
_requestHandlerDatas = new BufferBlock<IRequestHandlerData>(executionOption);
_webProxies = new TransformBlock<WebProxy, PingedWebProxy>(async webProxy => {
var isOnline =await requestDataflowData.Pinger.IsOnlineAsync(webProxy.Address).ConfigureAwait(false);
return new PingedWebProxy(webProxy.Address, isOnline);
}, executionOption);
var joinBlock = new JoinBlock<IRequestHandlerData, WebProxy>(new GroupingDataflowBlockOptions { Greedy = false });
_transformBlock = new TransformBlock<Tuple<IRequestHandlerData, WebProxy>, TOut>(async tuple => {
await requestDataflowData.LimitRateAsync(tuple.Item1, tuple.Item2).ConfigureAwait(false);
var #out = await requestDataflowData.GetResponseAsync<TOut>(tuple.Item1, tuple.Item2).ConfigureAwait(false);
_webProxies.Post(tuple.Item2);
_requestHandlerDatas.Post(tuple.Item1);
return #out;
}, executionOption);
var dataflowLinkOptions = new DataflowLinkOptions(){PropagateCompletion = true};
joinBlock.LinkTo(_transformBlock,dataflowLinkOptions,tuple => ((PingedWebProxy)tuple.Item2).IsOnline);
joinBlock.LinkTo(DataflowBlock.NullTarget<Tuple<IRequestHandlerData, WebProxy>>(),dataflowLinkOptions,tuple =>
!((PingedWebProxy) tuple.Item2).IsOnline);
_requestHandlerDatas.LinkTo(joinBlock.Target1,dataflowLinkOptions);
_webProxies.LinkTo(joinBlock.Target2,dataflowLinkOptions);
So, first I am posting data to _webProxies which verifies that I have a working proxy and if so it links it together with my _requestHandlerDatas in a _transformBlock that will use them to call another service. When that call returns I am releasing both the used parameters (proxy, handlerData) back to their pools and output the TOut for further processing from another block. My goal is to delay the webservice calls from the same proxy,handlerData combination.
I think this has something to do with my really bad async programming.
Here it goes. I am fetching mailchimp subscribers using an async method but the result just hangs and never returns to main thread.
The async method
public async Task<List<Subscriber>> GetMailChimpSubscribers()
{
TaskCompletionSource<List<Subscriber>> result = new TaskCompletionSource<List<Subscriber>>();
await Task.Run(async () =>
{
var subscribers = new List<Subscriber>();
var listId = "";
var members = await _manager.Members.GetAllAsync(listId);
foreach (var item in members)
{
var sub = new Subscriber();
sub.Email = item.EmailAddress;
subscribers.Add(sub);
}
result.SetResult(subscribers);
});
return result.Task.Result;
}
This hangs after result.SetResult(subscribers) statement completely.
This is called from
public static List<Subscriber> GetSubscribers()
{
MailchimpHelper helper = new MailchimpHelper();
var subscribers= helper.GetMailChimpSubscribers();
return subscribers.Result;
}
What exactly is wrong here? Is the setup wrong?
PS : there isn't an issue with mailchimp or the api , it works great in console. this is purely something bad with async programming
UPDATE:
In case this is faced by someone. the blog helped to clear a lot
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
the working solution from below answers and blog.
public async Task<List<Subscriber>> GetMailChimpSubscribers()
{
var subscribers = new List<Subscriber>();
var listId = "";
var members =
await _manager.Members.GetAllAsync(listId).**ConfigureAwait(false)**;
foreach (var item in members)
{
var sub = new Subscriber();
sub.Email = item.EmailAddress;
subscribers.Add(sub);
}
return subscribers;
}
public static List<Subscriber> GetSubscribers()
{
MailchimpHelper helper = new MailchimpHelper();
var subscribers= helper.GetMailChimpSubscribers();
subscribers.Wait();
return subscribers.Result;
}
There's a lot wrong here:
The actual problem is blocking on asynchronous code, which causes a deadlock.
There's also a confusing and unnecessary use of TaskCompletionSource<T>.
It also appears that the Task.Run is unnecessary.
Removing all the bad parts leaves us with:
public async Task<List<Subscriber>> GetMailChimpSubscribersAsync()
{
var subscribers = new List<Subscriber>();
var listId = "";
var members = await _manager.Members.GetAllAsync(listId);
foreach (var item in members)
{
var sub = new Subscriber();
sub.Email = item.EmailAddress;
subscribers.Add(sub);
}
return subscribers;
}
public static async Task<List<Subscriber>> GetSubscribersAsync()
{
MailchimpHelper helper = new MailchimpHelper();
return await helper.GetMailChimpSubscribersAsync();
}
Don't use .Result in environments with a synchronization context - it creates a circular wait.
In general, try to use async "all the way down" if possible - i.e. if you're going to use async/await at all, you should only use async/await.
Please see Don't Block on Async Code by Stephen Cleary for more details.
The first part of the problem is that you are, for some weird reason, wrapping the task inside another task. Change your method to this:
public async Task<List<Subscriber>> GetMailChimpSubscribers()
{
var subscribers = new List<Subscriber>();
var listId = "";
var members = await _manager.Members.GetAllAsync(listId);
foreach (var item in members) //this foreach could be a simpler LinQ statement
{
var sub = new Subscriber();
sub.Email = item.EmailAddress;
subscribers.Add(sub);
}
return subscribers;
}
You should also be calling this method using await, but if this is not possible then change your method to this:
public static List<Subscriber> GetSubscribers()
{
MailchimpHelper helper = new MailchimpHelper();
var subscribers = helper.GetMailChimpSubscribers();
subscribers.Wait();
return subscribers.Result;
}
I am trying to understand async programming, and I had a question. It is regarding the following functions below.
public async void TestAsyncCall() {
Task<string> TaskResult1 = DoSomethingAsync();
string Result2 = DoSomething();
string Result1 = await TaskResult1;
}
public string DoSomething() {
return "synch";
}
public async Task<string> DoSomethingAsync() {
await Task.Delay(10000);
return "asynch";
}
In the function call TestAsyncCall(), would one thread be used to execute DoSomethingAsync(), and another thread to execute DoSomething()?
Then when await is encountered, it would wait for DoSomethingAsync() to complete and release that thread (while also not blocking the original thread)?
Or will this not warrant any new threads being created? In that case will the DoSomethingAsync call be relevant only if it were to deal with some external resource?
I recommend you read my article on async ASP.NET.
Or will this not warrant any new threads being created?
This won't create any new threads. In particular, async and await by themselves won't create any new threads.
On ASP.NET, it's likely that the code after an await will run on a different thread than the code before that await. This is just exchanging one thread for another, though; no new threads are created.
In that case will the DoSomethingAsync call be relevant only if it were to deal with some external resource?
The primary use case for async is to deal with I/O, yes. This is particularly true on ASP.NET.
As #Stepehen-cleary said, "In particular, async and await by themselves won't create any new threads."
This next example is taken from the book: "C sharp in Depth" by John Skeet, chapter 15 pp.465:
class AsyncForm : Form
{
/* The first part of listing 15.1 simply creates the UI and hooks up an event handler for
the button in a straightforward way */
Label label;
Button button;
public AsyncForm()
{
label = new Label {
Location = new Point(10, 20),
Text = "Length"
};
button = new Button {
Location = new Point(10, 50),
Text = "Click"
};
button.Click += DisplayWebSiteLength;
AutoSize = true;
Controls.Add(label);
Controls.Add(button);
}
/* When you click on the button, the text of the book’s home page is fetched
and the label is updated to display the HTML lenght in characters */
async void DisplayWebSiteLength(object sender, EventArgs e)
{
label.Text = "Fetching...";
using (HttpClient client = new HttpClient())
{
string text =
await client.GetStringAsync("http://csharpindepth.com");
label.Text = text.Length.ToString();
}
}
/* The label is updated to display the HTML length in characters D. The
HttpClient is also disposed appropriately, whether the operation succeeds or fails—
something that would be all too easy to forget if you were writing similar asynchronous
code in C# 4 */
}
With this in mind, let's take a look to your code, you have Result1 and Result2, there's no point in having one asynchronous task waiting for a synchronous task to be finished. I would use Parallelism so you can perform both methods but to return something like two sets of Data, performing LINQ queries at the same time.
Take a look to this short example about Parallelism with Async Tasks:
public class StudentDocs
{
//some code over here
string sResult = ProcessDocs().Result;
//If string sResult is not empty there was an error
if (!sResult.Equals(string.Empty))
throw new Exception(sResult);
//some code over there
##region Methods
public async Task<string> ProcessDocs()
{
string sResult = string.Empty;
try
{
var taskStuDocs = GetStudentDocumentsAsync(item.NroCliente);
var taskStuClasses = GetStudentSemesterClassesAsync(item.NroCliente, vencimientoParaProductos);
//We Wait for BOTH TASKS to be accomplished...
await Task.WhenAll(taskStuDocs, taskStuClasses);
//Get the IList<Class>
var docsStudent = taskStuDocs.Result;
var docsCourses = taskStuClasses.Result;
/*
You can do something with this data ... here
*/
}
catch (Exception ex)
{
sResult = ex.Message;
Loggerdb.LogInfo("ERROR:" + ex.Message);
}
}
public async Task<IList<classA>> GetStudentDocumentsAsync(long studentId)
{
return await Task.Run(() => GetStudentDocuments(studentId)).ConfigureAwait(false);
}
public async Task<IList<classB>> GetStudentSemesterCoursessAsync(long studentId)
{
return await Task.Run(() => GetStudentSemesterCourses(studentId)).ConfigureAwait(false);
}
//Performs task to bring Student Documents
public IList<ClassA> GetStudentDocuments(long studentId)
{
IList<ClassA> studentDocs = new List<ClassA>();
//Let's execute a Stored Procedured map on Entity Framework
using (ctxUniversityData oQuery = new ctxUniversityData())
{
//Since both TASKS are running at the same time we use AsParallel for performing parallels LINQ queries
foreach (var item in oQuery.GetStudentGrades(Convert.ToDecimal(studentId)).AsParallel())
{
//These are every element of IList
studentDocs.Add(new ClassA(
(int)(item.studentId ?? 0),
item.studentName,
item.studentLastName,
Convert.ToInt64(item.studentAge),
item.studentProfile,
item.studentRecord
));
}
}
return studentDocs;
}
//Performs task to bring Student Courses per Semester
public IList<ClassB> GetStudentSemesterCourses(long studentId)
{
IList<ClassB> studentCourses = new List<ClassB>();
//Let's execute a Stored Procedured map on Entity Framework
using (ctxUniversityData oQuery = new ctxUniversityData())
{
//Since both TASKS are running at the same time we use AsParallel for performing parallels LINQ queries
foreach (var item in oQuery.GetStudentCourses(Convert.ToDecimal(studentId)).AsParallel())
{
//These are every element of IList
studentCourses.Add(new ClassB(
(int)(item.studentId ?? 0),
item.studentName,
item.studentLastName,
item.carreerName,
item.semesterNumber,
Convert.ToInt64(item.Year),
item.course ,
item.professorName
));
}
}
return studentCourses;
}
#endregion
}