UmbracoContext in New Thread to Populate Cache Asynchronously
I'm getting an ArgumentNullException when I try to do this from a new thread:
var helper = new UmbracoHelper(UmbracoContext.Current);
Basically, I'm trying to use the UmbracoHelper to access the published content so I can populate some caches in a static variable. I'd rather not hold the rendering of the page up for this, which is why I'm trying to do it in a new thread.
Is there an easy way to set the UmbracoContext from a new thread? I tried the following, but kept running into errors, so I'm thinking there must be a better way:
var originalHttpContext = HttpContext.Current;
var httpContext = new HttpContextWrapper(originalHttpContext);
var applicationContext = Umbraco.Core.ApplicationContext.Current;
var security = new WebSecurity(httpContext, applicationContext);
var preview = UmbracoContext.Current.InPreviewMode;
var t = new Thread(() => {
HttpContext.Current = originalHttpContext;
UmbracoContext.EnsureContext(httpContext, applicationContext, security, false, preview);
// ...Call various parts of the Umbraco API...
});
t.Start();
Rather than doing the above, I'm just going to make an asynchronous request to the page with WebClient using a special query string. When that query string is present, I'll know that the page was called by code rather than by a site visitor. In that code, I can do the caching.
Can you please post an example of your solution. I would really appreciate it as when attempting to do so myself I ran into routing, umbraco null reference exceptions, and publish issues (e.g. the virtual media folder in iis was being ignored and a default path used instead).
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Logging;
namespace MyNamespace
{
public class TaskManager
{
#region Variables
private static readonly string TaskKey = Guid.NewGuid().ToString("N");
#endregion
#region Properties
private static object TasksLock { get; set; }
private static Queue<Action> Tasks { get; set; }
#endregion
#region Constructors
/// <summary>
/// Static constructor.
/// </summary>
static TaskManager()
{
TasksLock = new object();
Tasks = new Queue<Action>();
}
#endregion
#region Public Methods
/// <summary>
/// Processes any queued tasks in an Umbraco context.
/// </summary>
/// <param name="request">
/// The current HTTP request.
/// </param>
/// <returns>
/// True, if this request is one intended to process requests; otherwise, false.
/// </returns>
/// <remarks>
/// To avoid blocking the thread, this method will make an extra web request in a
/// new thread and specify a query string to indicate to that request that the tasks
/// should be processed.
/// </remarks>
public static bool ProcessTasksInUmbracoContext(HttpRequestBase request)
{
// Variables.
var processMode = false;
// Check for special query string.
if (TaskKey.InvariantEquals(request.QueryString["TaskKey"]))
{
// Process tasks.
processMode = true;
lock (TasksLock)
{
if (Tasks.Any())
{
while (Tasks.Any())
{
try
{
var task = Tasks.Dequeue();
task();
}
catch (Exception ex)
{
LogHelper.Error<TaskManager>("Error while processing task in Umbraco context.", ex);
}
}
}
}
}
else
{
// Call tasks from homepage (to ensure a valid Umbraco Context).
lock (TasksLock)
{
if (Tasks.Any())
{
var uri = request.Url;
var builder = new UriBuilder(uri) {Path = null, Query = "TaskKey=" + TaskKey};
var url = builder.Uri.AbsoluteUri;
ActionUtility.SafeThread(() => (new WebClient()).DownloadString(url));
}
}
}
// Was in process mode?
return processMode;
}
/// <summary>
/// Adds a task to the queue.
/// </summary>
/// <param name="action">
/// The action to execute in an Umbraco context.
/// </param>
public static void AddTask(Action action)
{
lock (TasksLock)
{
Tasks.Enqueue(action);
}
}
#endregion
}
}
Then in my main CSHTML file, I called a function on that class:
if (TaskManager.ProcessTasksInUmbracoContext(Request))
{
return;
}
Anywhere I need a task to run on a different thread, I just call the AddTask function shown above to queue it up. Then, when somebody hits any page, all the tasks in the queue are processed on a different thread.
It does that by making a request back to the site with a special query string that normal visitors don't add. When it sees that query string, it just processes the tasks.
FYI, ActionUtility.SafeThread is just a function I created to ensure any exceptions are caught and suppressed. This is because threads that throw exceptions can cause problems for websites (i.e., IIS will restart the application pool if enough of them occur, based on whatever you have configured in IIS).
Note that this is an ugly workaround, but it works.
Thank you Nicholas. I imagine we were running into the same issues & errors as you so I will give your solution a go today. We are doing the custom setup for the benefit of a client with limited capability to speed up the backend admin processes. Something which quite a few of the clients with limited web experience have requested. (e.g. setup nodes, pre-population of fields with dynamic data, automatic creation of child nodes when the site is interacted with etc).
I had some different use cases, from the sound of it. Mine related to asynchronously handling long running tasks (e.g., extracting data from a few hundred content nodes) and doing things with published content in a new thread (e.g., when a file system watcher indicated an import file was written to the file system).
setup nodes, pre-population of fields with dynamic data, automatic creation of child nodes when the site is interacted with etc
Ended up with a similar process to the above example from Nicholas but had a separate database to store queued item data to process.
In this example a user submits event data with sub structure items in a form. What is intended is that later on the user could also batch process event data into nodes.
The form data gets processed and saved to a separate db for logging and storage before attempting the insert to Umbraco (generally a set of 7+ nodes with publish event hooks etc which made the initial submission time way too long as it would be waiting for Umbraco publishing to complete).
What gets returned from the query is the success state, new parent event Umbraco node id (if applicable), an error message (if applicable).
[GET]
public EventCreationResult ProcessEventToUmbraco(int eventPageQueueItemId)
{
if (eventPageQueueItemId == 0)
{
return new EventCreationResult
{
Success = false,
Key = "Post",
Message = "Reference code does not correspond to an existing event."
};
}
else
{
//get the event item by id from db or cache
var eventItem = CacheSingletons.EventQueueItemCacheProvider.GetEventQueueItem(eventPageQueueItemId);
if (eventItem == null || eventItem.Processed) {
return new EventCreationResult
{
Success = false,
Key = "Post",
Message = "Reference code does not correspond to an unprocessed event."
};
}
//set the event to have a processed flag set then perform the processing action, (note on error reset processed flag to false)
SetEventQueueItemProcessedFlag(eventItem, true);
//run the processing logic and return the result of the umbraco save process
//e.g. if one of the subnodes fails the parent gets deleted.
// The result is a fail & the eventPageQueueItem gets remarked as unprocessed & a user friendly message is returned
// if the event is processed successfully into umbraco the result is a success, the processed datetime will be set and the umbraco id returned
var result = ProcessEventNodesToUmbraco(eventItem, HttpContext);
return result;
}
}
So on form submit for a single event once validated and saved to the db the remote call to method above is sent
Given that the above uses a database to act as a intermediary stage for information the runtime of the parent thread is longer. (Db crud time). But otherwise
allows the admin user to remotely call the action if needed by id (or other properties). Ideally the action would be within a controller that checks for authorised backoffice users.
The setup for a TaskManager is a good idea and I am testing using that for the batch processing of unprocessed event items. This includes the use of locking objects etc to prevent the same call being run simultaneously.
At the moment the method data for reference is being passed through the query string. Setting it up to submit the full event object & reduce the db retrieve calls would not be to much more difficult.
UmbracoContext in New Thread to Populate Cache Asynchronously
I'm getting an ArgumentNullException when I try to do this from a new thread:
Basically, I'm trying to use the UmbracoHelper to access the published content so I can populate some caches in a static variable. I'd rather not hold the rendering of the page up for this, which is why I'm trying to do it in a new thread.
Is there an easy way to set the UmbracoContext from a new thread? I tried the following, but kept running into errors, so I'm thinking there must be a better way:
My workaround for anybody who is curious...
Rather than doing the above, I'm just going to make an asynchronous request to the page with WebClient using a special query string. When that query string is present, I'll know that the page was called by code rather than by a site visitor. In that code, I can do the caching.
Hello Nicholas,
Can you please post an example of your solution. I would really appreciate it as when attempting to do so myself I ran into routing, umbraco null reference exceptions, and publish issues (e.g. the virtual media folder in iis was being ignored and a default path used instead).
First, I created this class:
Then in my main CSHTML file, I called a function on that class:
Anywhere I need a task to run on a different thread, I just call the AddTask function shown above to queue it up. Then, when somebody hits any page, all the tasks in the queue are processed on a different thread.
It does that by making a request back to the site with a special query string that normal visitors don't add. When it sees that query string, it just processes the tasks.
FYI, ActionUtility.SafeThread is just a function I created to ensure any exceptions are caught and suppressed. This is because threads that throw exceptions can cause problems for websites (i.e., IIS will restart the application pool if enough of them occur, based on whatever you have configured in IIS).
Note that this is an ugly workaround, but it works.
For good measure, here's the implementation of SafeThread:
Thank you Nicholas. I imagine we were running into the same issues & errors as you so I will give your solution a go today. We are doing the custom setup for the benefit of a client with limited capability to speed up the backend admin processes. Something which quite a few of the clients with limited web experience have requested. (e.g. setup nodes, pre-population of fields with dynamic data, automatic creation of child nodes when the site is interacted with etc).
I had some different use cases, from the sound of it. Mine related to asynchronously handling long running tasks (e.g., extracting data from a few hundred content nodes) and doing things with published content in a new thread (e.g., when a file system watcher indicated an import file was written to the file system).
If you are talking about creating child nodes when a parent node of a particular doctype is created (for example), it sounds like you want to respond to the ContentService.Saved event: https://our.umbraco.org/documentation/reference/events/contentservice-events
From that point, you should be able to use the content service to create child nodes (and do a few other things, like pre-populate fields and such).
Ended up with a similar process to the above example from Nicholas but had a separate database to store queued item data to process.
In this example a user submits event data with sub structure items in a form. What is intended is that later on the user could also batch process event data into nodes. The form data gets processed and saved to a separate db for logging and storage before attempting the insert to Umbraco (generally a set of 7+ nodes with publish event hooks etc which made the initial submission time way too long as it would be waiting for Umbraco publishing to complete). What gets returned from the query is the success state, new parent event Umbraco node id (if applicable), an error message (if applicable).
So on form submit for a single event once validated and saved to the db the remote call to method above is sent
Given that the above uses a database to act as a intermediary stage for information the runtime of the parent thread is longer. (Db crud time). But otherwise allows the admin user to remotely call the action if needed by id (or other properties). Ideally the action would be within a controller that checks for authorised backoffice users.
The setup for a TaskManager is a good idea and I am testing using that for the batch processing of unprocessed event items. This includes the use of locking objects etc to prevent the same call being run simultaneously.
At the moment the method data for reference is being passed through the query string. Setting it up to submit the full event object & reduce the db retrieve calls would not be to much more difficult.
is working on a reply...